前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >7.volatile怎么通过内存屏障保证可见性和有序性?

7.volatile怎么通过内存屏障保证可见性和有序性?

作者头像
终有救赎
发布2023-10-16 10:28:43
3120
发布2023-10-16 10:28:43
举报
文章被收录于专栏:多线程

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并发专题《结丹篇》

  1. 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 读写锁怎么表示?

  1. ReentrantReadWriteLock 读写锁底层源码和机制深度剖析

JAVA并发专题《元神篇》并发数据结构篇

31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?

32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?

33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?

34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?

35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?

36.DelayQueue 底层源码剖析,延时队列怎么实现?

37.SynchronousQueue底层原理解析

JAVA并发专题《飞升篇》线程池底层深度剖析

  1. 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?

39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?

40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?

  1. ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?
  2. ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?
  3. ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?
  4. ThreadPoolExecutor shutdown、shutdownNow内部核心流程
  5. 再回头看看为啥不推荐Executors提供几种线程池?
  6. ThreadPoolExecutor线程池篇总结
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • volatile通过内存屏障保证可见性
  • volatile通过内存屏障保证有序性
  • 目录
    • JAVA并发专题 《筑基篇》
      • JAVA并发专题《练气篇》
        • JAVA并发专题《结丹篇》
          • JAVA并发专题《金丹篇》
            • JAVA并发专题《元神篇》并发数据结构篇
              • JAVA并发专题《飞升篇》线程池底层深度剖析
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档