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

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

volatile通过内存屏障保证可见性

小陈:老王,你上一篇抛出一个问题volatile怎么通过内存屏障保证可见性和有序性?我现在迫不及待的想知道了。

老王:嗯嗯,我们慢慢来讲,先说说volatile怎么通过内存屏障来保证可见性?

小陈:volatile关键字实际上是怎么使用内存屏障的呢?

老王:是这样子的。

volatile修饰的变量,在每个读操作(load操作)之前都加上Load屏障,强制从主内存读取最新的数据。每次在assign赋值后面,加上Store屏障,强制将数据刷新到主内存。

老王:以volatile int = 0;线程A、B进行 i++ 的操作来画图给你讲解一下:

image.png

如上图所示:

(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屏障

image.png

所以通过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不能保证原子性的问题......

相关文章
|
3月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
67 0
|
4月前
|
安全 Java 开发者
探索Java内存模型:可见性、有序性和并发
在Java的并发编程领域中,内存模型扮演了至关重要的角色。本文旨在深入探讨Java内存模型的核心概念,包括可见性、有序性和它们对并发实践的影响。我们将通过具体示例和底层原理分析,揭示这些概念如何协同工作以确保跨线程操作的正确性,并指导开发者编写高效且线程安全的代码。
|
4月前
|
缓存 安全 Java
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
72 4
|
4月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
70 1
|
4月前
|
设计模式 缓存 安全
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
57 1
|
4月前
|
存储 缓存 Java
(一) 玩命死磕Java内存模型(JMM)与 Volatile关键字底层原理
文章的阐述思路为:先阐述`JVM`内存模型、硬件与`OS`(操作系统)内存区域架构、`Java`多线程原理以及`Java`内存模型`JMM`之间的关联关系后,再对`Java`内存模型进行进一步剖析,毕竟许多小伙伴很容易将`Java`内存模型(`JMM`)和`JVM`内存模型的概念相互混淆,本文的目的就是帮助各位彻底理解`JMM`内存模型。
|
4月前
|
微服务
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
|
4月前
|
存储 缓存 安全
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
34 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
297 0
|
5天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
13 1