volatile通过内存屏障保证可见性
小陈:老王,你上一篇抛出一个问题volatile怎么通过内存屏障保证可见性和有序性?我现在迫不及待的想知道了。
老王:嗯嗯,我们慢慢来讲,先说说volatile怎么通过内存屏障来保证可见性?
小陈:volatile关键字实际上是怎么使用内存屏障的呢?
老王:是这样子的。
volatile修饰的变量,在每个读操作(load操作)之前都加上Load屏障,强制从主内存读取最新的数据。每次在assign赋值后面,加上Store屏障,强制将数据刷新到主内存。
老王:以volatile int = 0;线程A、B进行 i++ 的操作来画图给你讲解一下:
如上图所示:
(1)线程A读取 i 的值遇到Load屏障,需要强制从主存读取得到 i = 0; 然后传递给工作线程执行++操作
(2)cpu执行 i++ 操作得到 i = 1,执行assign指令进行赋值;然后遇到Store屏障,需要强制刷新回主内存,此时得到主内存 i = 1
(3)然后线程B执行读取 i 遇到Load屏障,强制从主内存读取,得到最新的值 i = 1,然后传给工作线程执行 ++操作,得到 i = 2,同样在赋值后遇到Store屏障立即将数据刷新回主内存
老王:通过上面的图和讲解,以及volatile读取前加的Load屏障、赋值后加的Store屏障看懂了吗?
小陈:哦哦,通过这样说我就明白了。
其实说白了就是通过一个屏障让volatile的变量每次读都读主存,每次修改后立即刷到主存里面。
好比线程A修改 i 后立即将值刷到主存里面,后面线程B用到的时候强制从主存读取,这个时候它能看到的值是线程A修改之后的值了。也就是通过这种方式来保证多线程之间的可见性吧。
老王:嘿嘿,没错;就是这个意思......
小陈:volatile通过内存屏障每次走主存的方式;这样来保障可见性,我理解了,害~,感觉也不难嘛......
老王:哈哈,这个本来就不难,只是你需要先了解一下内存屏障,以及这些屏障的作用是什么。再加上之前讲过多核CPU高速缓存、JAVA内存模型,你理解起来就很容易了。如果没有之前的知识做铺垫,你理解起来就费劲了......
小陈:哈哈,好像也是啊......
老王:好了,下面我们继续来讨论一下volatile怎么通过内存屏障来保证有序性?
volatile通过内存屏障保证有序性
老王:小陈啊,之前讲过一个有序性问题导致异常的例子,你还记得不?
小陈:记得啊,我记得当时是这样说的:
线程A的执行代码:
// 步骤1 dataSource = initDataSource(); // 步骤2 httpClient = initHttpClient(); // 步骤3 initOK = true;
线程B的执行代码:
// 步骤4 while(!initOK) { } // 步骤5 Object data = dataSource.getData(); // 步骤6 httpClient.request(data);
由于线程A先执行了initOK = true。导致线程B提前跳出了while循环!!! ,然后线程B调用dataSource.getData的时候发现dataSource没初始化好,竟然是个坑爹的null,导致代码报错了。
老王:哈哈,看来你很用功啊;之前的例子你都记得。
老王:现在我们就来讲讲将initOk用volatile来修饰,是可以做到线程A有序性执行的。
好了,废话不多说,我先来上代码:
// 步骤1 dataSource = initDataSource(); // 步骤2 httpClient = initHttpClient(); // 步骤3 initOK = true;
对应到指令可能是这样的:
// 步骤1 对应上面dataSource = initDataSource(); store datasource指令 // 步骤2 对应上面httpClient = initHttpClient(); store http指令 StoreStore屏障 (注意:在store initOK前面加了一个StoreStore屏障) // 步骤3 对应上面initOK = true; store initOk = true指令 StoreLoad 屏障 (注意:在store initOK后面加了一个StoreLoad屏障)
注意这里:store initOk指令的前面加了一道StoreStore屏障;后面加了一道StoreLoad屏障
所以通过volatile修饰initOK,加了屏障之后;store initOK = true 这一条指令是不能跳到store dataSource、store http前面去的,所以必须****先执行完前面的执行之后,才能执行store initOK = true
这样对于线程B来说,加了内存屏障之后,它看到线程A就是资源初始化完成之后,才将initOK表示设置为true的,这样它看到线程A的执行就是有序的
老王:小陈,我这么说你懂了不?
小陈:稍等,我来捋捋思路....
也就是通过加了屏障,store initOK = true 指令不能跟前面的store指令进行交换。所以它就自然得等前面的store指令执行完了之后,才执行store initOK = true的对吧? 然后在线程B那一侧看到的initOK = true的时候,发现资源以及初始化好了,自然就不会报错了。
老王:bingo,就是这个道理....
小陈:这个volatile写的时候前面加StoreStore屏障、写的后面加StoreLoad屏障来禁止重排序的我看懂了。当volatile读的时候加什么屏障来禁止重排序?
老王:这个就当作思考题,你自己再去看看咯,原理也是一样的......
小陈:好的老王,在线程安全来说volatile保证了可见性、有序性了;我看过一些资料说volatile是不能保证原子性的,那它为啥不能保证原子性啊?
老王:今天我们先讲到这里,你先消化消化。我们下一章再来讨论volatile不能保证原子性的问题......