volatile关键字
volatile是java虚拟机提供的最轻量级的同步机制。例如,我们生命一个volatile变量volatile int x = 0,它表达的是:告诉编译器,对这个变量的读写,不能使用CPU缓存,必须从内存中读取或者写入。被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读现象。
1.volatile的实现原理
在生成汇编代码时候,会在volatile修饰的共享变量进行写操作的时候多出Lock前缀指令。
Lock前缀指令对于多核处理器主要有以下两方面的影响:
1.将当前处理器缓存行的数据写回系统内存;
2.这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效。
为了提高处理速度,处理器不直接和内存进行通信,而是将系统内存的数据读到内部缓存后再进行操作,但操作不知道什么时候会写到内存。如果声明了volatile的变量进行写操作,JVM会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写入内存。但是就算写回内存,如果其他处理器缓存的仍然是旧值,再执行计算还是有问题。所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器缓存行设置为无效状态,当处理器对这个数据进行修改的时候,会重新从系统把数据读到处理器缓存里。所以我们可以得出以下结论:
1.lock前缀指令会使缓存写回内存
2.一个处理器的缓存写回内存会导致其他处理器缓存无效。
3.当处理器发现本地缓存无效,就会从内存中重读该变量数据的最新值。
2.volatile的happens-before关系
在6条happens-before规则中有一条是:volatile变量规则
对一个volatie域的写happens-before于任意后续对这个volatile域的读
public class VolatileExample {
private int a = 0;
private volatile boolean flag = false;
public void writer(){
a = 1; //1
flag = true; //2
}
public void reader(){
if(flag){ //3
int i = a; //4
}
}
}
我们可以让线程A执行writer方法,然后让线程B执行reader方法。
writer方法中,会修改共享变量a,然后修改volatile变量flag。
reader方法会读取volatile变量。
根据happens-before规则,可以得到flag = true操作对于线程B是可见的。
3.volatile内存语义实现
为了性能优化,JMM在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序,可以通过添加内存屏障的方法阻止重排序。
JMM的内存屏障分为以下四类:

