小陈:老王,你上一篇抛出一个问题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怎么通过内存屏障来保证有序性?
老王:小陈啊,之前讲过一个有序性问题导致异常的例子,你还记得不?
小陈:记得啊,我记得当时是这样说的:
线程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不能保证原子性的问题......
4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?
7.volatile怎么通过内存屏障保证可见性和有序性?
8.volatile为啥不能保证原子性?
9.synchronized是个啥东西?应该怎么使用?
10.synchronized底层之monitor、对象头、Mark Word?
11.synchronized底层是怎么通过monitor进行加锁的?
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
13.synchronized怎么保证可见性、有序性、原子性?
15.unsafe类的CAS是怎么保证原子性的?
16.Atomic原子类体系讲解
17.AtomicInteger、AtomicBoolean的底层原理
18.AtomicReference、AtomicStampReference底层原理
19.Atomic中的LongAdder底层原理之分段锁机制
20.Atmoic系列Strimped64分段锁底层实现源码剖析
21.AQS是个啥?为啥说它是JAVA并发工具基础框架?
22.基于AQS的互斥锁底层源码深度剖析
23.基于AQS的共享锁底层源码深度剖析
24.ReentrantLock是怎么基于AQS实现独占锁的?
25.ReentrantLock的Condition机制底层源码剖析
26.CountDownLatch 门栓底层源码和实现机制深度剖析
27.CyclicBarrier 栅栏底层源码和实现机制深度剖析
28.Semaphore 信号量底层源码和实现机深度剖析
29.ReentrantReadWriteLock 读写锁怎么表示?
31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?
32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?
33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?
34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?
35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?
36.DelayQueue 底层源码剖析,延时队列怎么实现?
37.SynchronousQueue底层原理解析
39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?
40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?