前篇博客 中已经阐述了 volatile 的特性了:
下面 LZ 就通过 happens-before 原则和 volatile 的内存语义两个方向介绍 volatile。
在这篇博客【死磕 Java 并发】—–Java 内存模型之 happend-before 中 LZ 阐述了 happens-before 是用来判断是否存数据竞争、线程是否安全的主要依据,它保证了多线程环境下的可见性。下面我们就那个经典的例子来分析 volatile 变量的读写建立的 happens-before 关系。
- public class VolatileTest {
- int i = 0;
- volatile boolean flag = false;
- //Thread A
- public void write(){
- i = 2; //1
- flag = true; //2
- }
- //Thread B
- public void read(){
- if(flag){ //3
- System.out.println("---i = " + i); //4
- }
- }
- }
依据 happens-before 原则,就上面程序得到如下关系:
操作 1、操作 4 存在 happens-before 关系,那么 1 一定是对 4 可见的。可能有同学就会问,操作 1、操作 2 可能会发生重排序啊,会吗?如果看过 LZ 的博客就会明白,volatile 除了保证可见性外,还有就是禁止重排序。所以 A 线程在写 volatile 变量之前所有可见的共享变量,在线程 B 读同一个 volatile 变量后,将立即变得对线程 B 可见。
在 JMM 中,线程之间的通信采用共享内存来实现的。volatile 的内存语义是:
所以 volatile 的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。
那么 volatile 的内存语义是如何实现的呢?对于一般的变量则会被重排序,而对于 volatile 则不能,这样会影响其内存语义,所以为了实现 volatile 的内存语义 JMM 会限制重排序。其重排序规则如下:
翻译如下:
volatile 的底层实现是通过插入内存屏障,但是对于编译器来说,发现一个最优布置来最小化插入内存屏障的总数几乎是不可能的,所以,JMM 采用了保守策略。如下:
StoreStore 屏障可以保证在 volatile 写之前,其前面的所有普通写操作都已经刷新到主内存中。
StoreLoad 屏障的作用是避免 volatile 写与后面可能有的 volatile 读 / 写操作重排序。
LoadLoad 屏障用来禁止处理器把上面的 volatile 读与下面的普通读重排序。
LoadStore 屏障用来禁止处理器把上面的 volatile 读与下面的普通写重排序。
下面我们就上面那个 VolatileTest 例子分析下:
- public class VolatileTest {
- int i = 0;
- volatile boolean flag = false;
- public void write(){
- i = 2;
- flag = true;
- }
- public void read(){
- if(flag){
- System.out.println("---i = " + i);
- }
- }
- }
上面通过一个例子稍微演示了 volatile 指令的内存屏障图例。
volatile 的内存屏障插入策略非常保守,其实在实际中,只要不改变 volatile 写 - 读得内存语义,编译器可以根据具体情况优化,省略不必要的屏障。如下(摘自方腾飞 《Java 并发编程的艺术》):
- public class VolatileBarrierExample {
- int a = 0;
- volatile int v1 = 1;
- volatile int v2 = 2;
- void readAndWrite(){
- int i = v1; //volatile读
- int j = v2; //volatile读
- a = i + j; //普通读
- v1 = i + 1; //volatile写
- v2 = j * 2; //volatile写
- }
- }
没有优化的示例图如下:
我们来分析上图有哪些内存屏障指令是多余的
1:这个肯定要保留了
2:禁止下面所有的普通写与上面的 volatile 读重排序,但是由于存在第二个 volatile 读,那个普通的读根本无法越过第二个 volatile 读。所以可以省略。
3:下面已经不存在普通读了,可以省略。
4:保留
5:保留
6:下面跟着一个 volatile 写,所以可以省略
7:保留
8:保留
所以 2、3、6 可以省略,其示意图如下:
来源: http://blog.csdn.net/chenssy/article/details/56679728