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的执行代码:
代码语言:javascript复制// 步骤1
dataSource = initDataSource();
// 步骤2
httpClient = initHttpClient();
// 步骤3
initOK = true;
线程B的执行代码:
代码语言:javascript复制// 步骤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有序性执行的。
好了,废话不多说,我先来上代码:
代码语言:javascript复制// 步骤1
dataSource = initDataSource();
// 步骤2
httpClient = initHttpClient();
// 步骤3
initOK = true;
对应到指令可能是这样的:
代码语言:javascript复制// 步骤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不能保证原子性的问题......
目录
JAVA并发专题 《筑基篇》
1.什么是CPU多级缓存模型?
2.什么是JAVA内存模型?
3.线程安全之可见性、有序性、原子性是什么?
4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?
JAVA并发专题《练气篇》
5.volatile怎么保证可见性?
6.什么是内存屏障?具有什么作用?
7.volatile怎么通过内存屏障保证可见性和有序性?
8.volatile为啥不能保证原子性?
9.synchronized是个啥东西?应该怎么使用?
10.synchronized底层之monitor、对象头、Mark Word?
11.synchronized底层是怎么通过monitor进行加锁的?
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
13.synchronized怎么保证可见性、有序性、原子性?
JAVA并发专题《结丹篇》
- JDK底层Unsafe类是个啥东西?
15.unsafe类的CAS是怎么保证原子性的?
16.Atomic原子类体系讲解
17.AtomicInteger、AtomicBoolean的底层原理
18.AtomicReference、AtomicStampReference底层原理
19.Atomic中的LongAdder底层原理之分段锁机制
20.Atmoic系列Strimped64分段锁底层实现源码剖析
JAVA并发专题《金丹篇》
21.AQS是个啥?为啥说它是JAVA并发工具基础框架?
22.基于AQS的互斥锁底层源码深度剖析
23.基于AQS的共享锁底层源码深度剖析
24.ReentrantLock是怎么基于AQS实现独占锁的?
25.ReentrantLock的Condition机制底层源码剖析
26.CountDownLatch 门栓底层源码和实现机制深度剖析
27.CyclicBarrier 栅栏底层源码和实现机制深度剖析
28.Semaphore 信号量底层源码和实现机深度剖析
29.ReentrantReadWriteLock 读写锁怎么表示?
- ReentrantReadWriteLock 读写锁底层源码和机制深度剖析
JAVA并发专题《元神篇》并发数据结构篇
31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?
32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?
33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?
34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?
35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?
36.DelayQueue 底层源码剖析,延时队列怎么实现?
37.SynchronousQueue底层原理解析
JAVA并发专题《飞升篇》线程池底层深度剖析
- 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?
39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?
40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?
- ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?
- ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?
- ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?
- ThreadPoolExecutor shutdown、shutdownNow内部核心流程
- 再回头看看为啥不推荐Executors提供几种线程池?
- ThreadPoolExecutor线程池篇总结