当声明一个volatile变量时,对这个变量的读/写会变得很特别。现在我们就来揭开volatile的神秘面纱。
volatile自身具有以下特性:
- 可见性。对一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入。
- 原子性。对任意一个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
但volitile对线程内存可见性的影响比volatile自身的特性更为重要,也更需我们去关注。
volatile写的内存语义如下:
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值全部刷新到主内存。
volatile读的内存语义如下:
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中去读取共享变量。
总结:
也就是说A线程写一个volitile变量后,B线程读取同一个volatile变量,在A线程写volitle操作之前的任何共享变量,在B线程读取同一个volatile变量之后,将立刻对B线程可见。
举一个例子:
public class VolatileExample { private volatile boolean flag=false; private int a=0; public void write(){ a=1; //1 flag=true; //2 } public void read(){ if(flag){ int y=a; //3 System.out.println(y); //4 输出1 } } }
在这段代码里面,根据happen-before规则,这个过程建立的happen-before规格如下:
1.根据程序次序规则:1 happen-before 2,3 happen-before 4;
2.根据volatile读写规格:2 happen-before 3
3.根据传递性规则:1 happen-before 4;
因此A线程中对a的写,永远对B线程可见。即使a不是volatile变量。这就是flag这个volatile变量对内存可见性的作用,当对一个线程对volatile变量进行写操作时,会把该线程本地内存中的共享变量全部刷新到主内存中去,当一个线程读一个volatile变量时,JMM会将该线程的本地内存置为无效,因此线程会去共享内存中读取变量。所以在上面例子中,B线程读取a的值,A已经刷新到主内存中的新值1。
对volatile的内存语义进行总结:
- 线程A写一个volatile变量,实际上是线程A向接下来要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
- 线程B读一个volatile变量,实际上是接收了之前某个线程所发出的(在写这个volatile变量之前对共享变量所做修改的消息)
- 线程A写一个volatile变量,随后线程B读一个volatile变量,这个过程实际上就是线程A通过主内存向线程B发送消息。