Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
128 lines (89 sloc) 3.42 KB
layout title key tags category date modify_date
article
001-Java基础-volatile关键字
A20190409012
编程 JAVA基础
blog
2019-04-09 22:19:12 +0800
2019-04-09 22:19:11 +0800

Java内存模型

  • 所有变量都保存在主内存中
  • 每个线程都有自己独立的工作内存
  • 线程对变量的操作(读/写)都必须在工作内存中

例如下面的代码

int i = 10; 

java中,必须先在工作内存中更新变量i,然后再刷新到主内存,而不是直接刷新到主内存。

volatile的作用

从线程安全的可见性、原子性、有序性分析volatile的作用。

volatile能够保证可见性

volatile能够用来修饰变量。

被volatile修饰过的变量,在任意一个线程的工作内存中被修改后,会立刻刷新到主内存。

同时通知其它线程,将其工作内存中的该变量置为失效,如果要对该变量进行操作,则需要重新从主内存中读取。

总结一下,就是volatile解决了线程安全中的可见性问题。

例如下面的代码。

// 线程1
boolean stop = false;
while(!stop){
    doSomething();
}

// 线程2
stop = true;
  • 线程1先将stop=false读取到工作内存。
  • 线程2修改stop=true,然后没有将值刷新到主内存。
  • 线程1仍然认为stop=false,继续执行循环中的代码。

如果用volatile修改stop变量,当线程2修改stop时,会立刻将新值刷新到主内存,并通知线程1失效其工作内存的缓存,重新从主内存读取。

volatile不能保证原子性

volatile不能保证原子性,例如下面的代码

public class VolatileDemo {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final VolatileDemo demo = new VolatileDemo();
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        demo.increase();
                }
            }.start();
        }

        // 保证所有线程都执行完
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(demo.inc);
    }
}

最后的输出并不总是期望的10000,原因如下:

  • 线程1从工作内存中读取inc当前值x,然后被阻塞。
  • 线程2从工作内存中读取inc当前值x,并对其做加1操作。
  • 线程1从阻塞中恢复,虽然inc在主内存中的值已更新,但是线程1不会再次读取。

如果想要保证原子性,通过Synchronize、Lock、AtomicInteger等方式均可以实现。

volatile可以部分保证有序性

Java内存模型中,volatile会禁止指令重排,它确保指令重排序时不会把其后面的指令排到其之前的位置,也不会把前面的指令排到其后面。

例如下面的例子:

//x、y为非volatile变量
//flag为volatile变量
x = 2;        //语句1
y = 0;        //语句2
flag = true;  //语句3
x = 4;        //语句4
y = -1;       //语句5

由于volatile关键字的存在,语句1和语句2一定是在语句3之前执行。

而语句4和语句5一定在语句3之后执行。

You can’t perform that action at this time.