网易首页 > 网易号 > 正文 申请入驻

深入理解 Volatile 关键字

0
分享至

volatile 关键字是 Java 语言的高级特性,但要弄清楚其工作原理,需要先弄懂 Java 内存模型。

初学volatile 关键字,我们需要弄清楚它到底意味着什么。总的来说,它有两个含义,分别是:

  • 保证可见性
  • 禁止指令重排序

保证可见性

保证可见性指的是:当一个线程修改了某个变量时,其他所有线程都知道该变量被修改了。 由于 volatile 可以保证可见性,因此 Java 能够保证现在在读取 volatile 变量时,线程读取到的值是准确的。但是这并不意味着对 volatile 变量的操作是线程安全的,因为有可能在读取到变量之后,又有其他线程对变量进行修改了。

为了说明这个问题,我们可以举个简单地例子。下面代码发起了 20个线程,每个线程对 race 变量进行 1 万次自增操作。如果这段代码能够正确并发执行,那么最后输出的结果应该是 20 万。但实际上,每次输出的结果都不一样,都是一个小于 20 万的数字,为什么呢?

这是因为当线程在获取到 race 变量的值,然后对其进行自增这中间,有可能其他线程对 race 变量做了自增操作,然后写回了主内存。而当前线程再将数据写回主内存时,就发生了数据覆盖。因此,就发生了数据不一致的问题。

要使得 volatile 变量不发生并发安全问题,只需要遵守如下两条规则即可:

运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

变量不需要与其他的状态变量共同参与不变约束。

第一条规则比较好理解,例如上面例子的 race 变量,其运算结果就依赖于变量的当前值,所以其并不符合第一条规则,因此就会有线程安全问题。但如果 race++ 变成了 race=1; 这样的情况,那么 race 的值就不依赖变量当前值,因此就不会有线程安全问题。

第二条规则有点晦涩难懂。其意思是说,变量不能和其他变量一起参与判断,无论其他变量是否是 volatile 类型的变量。例如 if(a && b) 这个判断就无法满足 volatile 的第二条规则,会发生线程安全问题,即使这两个变量都是 volatile 类型的变量。

关于第二条规则的描述,为啥与其他变量一起,就没法保证线程安全呢?

要解答这个问题,我们不妨假设一下各种可能的场景。

我们假设变量 a b 的初始值都是 true,并且两者都是 volatile 类型变量。

场景一:线程 A 执行 if(a && b) 判断,先判断变量 a,发现是 true,于是继续判断变量 b。发现变量 b 也是 true,于是整个表达式为 true。

场景二:线程 A 执行 if(a && b) 判断,先判断变量 a,发现是 true。此时线程 B 修改了变量 b 的值为 false。接着线程 A 继续判断变量 b 的值,发现变量 b 的值为 false。于是整体表达式的值为 false。

通过上面的例子,我们发现同样的表达式在不同的并发场景下会有不同的结果,这很明显就是线程不安全的。因为线程安全的代码,在单线程和多线程下,其结果应该是一样的。

禁止指令重排序

指令重排序,指的是硬件层面为了加快执行速度,可能会调整指令的执行顺序,从而会出现并不按代码顺序的执行情况出现。例如下面的代码里,我们初始化了 flag 变量为 false,然后再将 flag 变量置为 true。但这样的代码在并发执行的时候,有可能先将 flag 职位 true,再将 flag 变为 false,从而发生线程安全问题。

boolean flag = false;
flag = true;

我们说 volatile 变量禁止指令重排序,其实就是指被 volatile 修饰的变量,其执行顺序不能被重排序。 禁止重排序的实现,是使用了一个叫「内存屏障」的东西。简单地说,内存屏障的作用就是指令重排序时,不能把后面的指令重排序到内存屏障之前的位置。

可见性的来源

我们前面说过:volatile 修饰的变量,当其被修改之后,其他变量就能立即获取到其变化。但这个可见性的来源是哪里呢?为什么其能够实现这样的可见性呢?其实 volatile的这些功能来源于 Java 内存模型中对 volatile 变量定义的特殊规则。

假定 T 表示一个线程,V 和 W 分别表示两个 volatile 型变量。在 Java 内存模型中规定在进行 read、load、use、assign、store 和 write 操作时需要满足如下规则:

  • 只有当线程 T 对变量 V 执行的前一个动作是 load 的时候,线程 T 才能对变量 V 执行 use 动作。并且,只有当线程 T 对变量 V 执行的后一个动作是 use 的时候,线程 T 才能对变量 V 执行 load 动作。
  • 只有当线程 T 对变量 V 执行的前一个动作是 assign 的时候,线程 T 才能对变量 V 执行 store 动作;并且,只有当线程 T 对变量 V 执行的后一个动作是 store 的时候,线程 T 才能对变量 V 执行 assign 动作。
  • 假定动作 A 是线程 T 对变量 V 实施的 use 或 assign 动作,假定动作 F 是和动作 A 相关联的 load 或 store 动作,假定动作 P 是和动作 F 相应的对变量 V 的 read 或 write 动作。类似的,假定动作 B 是线程 T 对变量 W 实施的 use 或 assign 动作,假定动作 G 是和动作 B 相关联的 load 或 store 动作,假定动作 Q 是和动作 G 相应的对变量 W 的 read 或 write 动作。如果 A 先于 B,那么 P 先于 Q。

上面三条规则有点复杂,我们来一条条讲解下。

首先,我们来看看第一条规则。

只有当线程 T 对变量 V 执行的前一个动作是 load 的时候,线程 T 才能对变量 V 执行 use 动作。

load 动作,指的是把从主内存得到的变量值,放入到工作内存的变量副本。use 动作,指的是将工作内存的一个变量值,传递给执行引擎。那么这句话合起来的意思可以理解为:要使用变量 V 之前,必须去主内存读取变量 V。

并且,只有当线程 T 对变量 V 执行的后一个动作是 use 的时候,线程 T 才能对变量 V 执行 load 动作。

这句的意思可以理解为:要去读取主内存的变量值放入工作内存的变量副本,那就必须使用它。

总的来说,这条规则的意思是:线程对变量 V 的 use 动作,必须与 read、load 动作连在一起,即 read -> load -> use 必须一起出现。这条规则要求在工作内存中,每次使用 V 前都必须先从主内存刷新最新的值,用于保证能看见其他线程对变量 V 所做的修改后的值。

我们继续看第二条规则。

只有当线程 T 对变量 V 执行的前一个动作是 assign 的时候,线程 T 才能对变量 V 执行 store 动作。

assign 动作,指的是将执行引擎的值赋值给工作内存的变量。store 动作,指的是将工作内存的一个变量传送到主内存,方便后续写回主内存。那么这句话合起来的意思可以理解为:要讲工作内存的变量写回主内存,那么必须是工作内存的变量收到执行引擎的赋值。

并且,只有当线程 T 对变量 V 执行的后一个动作是 store 的时候,线程 T 才能对变量 V 执行 assign 动作。

这句话的意思可以理解为:要将执行引擎接收到的值赋给工作内存的变量,就必须把工作内存变量的值写回主内存。

总的来说,这条规则的意思是:线程对变量 V 的 assign 动作,必须与 store、write 连在一起,即:assign -> store -> write 必须一起出现。这条规则要求在工作内存中,每次修改 V 后都必须立刻同步回主内存中,用于保证其他线程可以看到自己对变量 V 所做的修改。

我们继续看第三条规则。

假定动作 A 是线程 T 对变量 V 实施的 use 或 assign 动作,假定动作 F 是和动作 A 相关联的 load 或 store 动作,假定动作 P 是和动作 F 相应的对变量 V 的 read 或 write 动作。

这句话意思比较简单,use 和 assign 动作分别是从工作内存传递变量给执行引擎,以及从执行引擎传递变量给工作内存。load 和 store 动作分别是从主内存载入数据到工作内存,以及从工作内存写数据到主内存。read 和 write 动作分别是将数据读取到工作内存,以及将数据写回主内存。

我们假设是一个写入到主内存动作,如果这几个组合起来,那么就是:A -> F -> P(assign -> store -> write)。

类似的,假定动作 B 是线程 T 对变量 W 实施的 use 或 assign 动作,假定动作 G 是和动作 B 相关联的 load 或 store 动作,假定动作 Q 是和动作 G 相应的对变量 W 的 read 或 write 动作。

与上面类似,如果是一个写入到主内存动作,如果这几个组合起来,那么就是:B -> G -> Q(assign -> store -> write)。

如果 A 先于 B,那么 P 先于 Q。

这个的意思是,如果 A 动作早于 B 动作发生,那么 A 动作对应的 P 动作(write 动作)就要早于 Q 动作 (write 动作)。

这条规则要求 volatile 修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同。

所以说 volatile 变量的可见性以及禁止重排序的语义,其实都来源于 Java 内存模型里对于 volatile 变量的定义。

总结

这篇文章,我们介绍了 volatile 的两个语义:

  • 可见性
  • 禁止重排序

可见性指的是 volatile 类型的变量,其变量值一旦被修改,其他线程就能够立刻感知到。而禁止重排序指的是被 volatile 修饰的变量,其执行顺序不能被重排序。我们在日常使用中,如果要使 volatile 变量不发生线程安全问题,只需要遵守下面两个规则即可。

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
  • 变量不需要与其他的状态变量共同参与不变约束。

最后,我们进一步探究了 volatile 可见性以及禁止重排序的来源,其实就是 Java 内存模型里来源: 陈树义

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
突发!菲律宾军机坠毁

突发!菲律宾军机坠毁

笔墨V
2024-05-01 01:10:43
你错过了多少异性给你的暗示?

你错过了多少异性给你的暗示?

户外阿崭
2024-04-08 07:35:07
英首相称中国“威权主义国家”,中方对他的称呼变了,性质严重了

英首相称中国“威权主义国家”,中方对他的称呼变了,性质严重了

博览历史
2024-04-30 14:41:49
雄鹿大胜,贝弗利冲突,谁注意字母哥反应,低迷之人打铁散步不该

雄鹿大胜,贝弗利冲突,谁注意字母哥反应,低迷之人打铁散步不该

东球弟
2024-05-01 12:04:08
性爱最大的敌人:1个字

性爱最大的敌人:1个字

闻心品阁
2024-04-30 22:57:24
拒绝美驻军!向中方发出邀请,解放军奔赴草原,蒙古这次学聪明了

拒绝美驻军!向中方发出邀请,解放军奔赴草原,蒙古这次学聪明了

文雅笔墨
2024-04-30 18:15:53
美国强行决战,已做好5年内对华开战准备?中国枕戈待旦无所惧

美国强行决战,已做好5年内对华开战准备?中国枕戈待旦无所惧

兵国大事
2024-04-29 17:37:47
“与辉同行”全员完成切割,董宇辉等9位主播名字全部去东方化

“与辉同行”全员完成切割,董宇辉等9位主播名字全部去东方化

校长侃财
2024-04-29 13:04:48
中央政治局会议,释放楼市重要信号

中央政治局会议,释放楼市重要信号

小白读财经
2024-04-30 15:58:40
哈姆下课?湖人新主帅候选曝光,3选1,詹姆斯老友或黑马逆袭

哈姆下课?湖人新主帅候选曝光,3选1,詹姆斯老友或黑马逆袭

东球弟
2024-05-01 11:10:05
诺伊尔谈金玟哉:失误也是足球的一部分,他的表现不全是坏的

诺伊尔谈金玟哉:失误也是足球的一部分,他的表现不全是坏的

懂球帝
2024-05-01 07:50:07
下月起,内地居民去港澳停留时间延长至14天!

下月起,内地居民去港澳停留时间延长至14天!

肇庆之星
2024-05-01 00:03:13
政治局会议来了,13大要点!

政治局会议来了,13大要点!

成方街哨兵
2024-04-30 17:02:45
迈腾男捉奸暴打女友后续:双方家庭已知情,曝出轨对象也有女友

迈腾男捉奸暴打女友后续:双方家庭已知情,曝出轨对象也有女友

180°视角
2024-04-30 11:46:37
理想汽车:全新理想L6上市12天累计定单突破20000台

理想汽车:全新理想L6上市12天累计定单突破20000台

三言科技
2024-04-30 16:19:10
嘴哥离开三只羊,流量惨淡,带货0销量!称:以前直播都是10万

嘴哥离开三只羊,流量惨淡,带货0销量!称:以前直播都是10万

娱乐圈酸柠檬
2024-05-01 10:07:13
许秋怡回母校献唱!丈夫王书麒苍老似胡枫,传身家过亿儿子超帅气

许秋怡回母校献唱!丈夫王书麒苍老似胡枫,传身家过亿儿子超帅气

娱乐八卦木木子
2024-05-01 09:47:01
中国又被拍了,美国卫星在甘肃拍到了什么?联合国都被“惊动”了

中国又被拍了,美国卫星在甘肃拍到了什么?联合国都被“惊动”了

书侃小吏史
2024-04-30 07:20:23
不走了!曝上海男篮功勋老臣拒绝退役,季后赛曾多次扮演救火奇兵

不走了!曝上海男篮功勋老臣拒绝退役,季后赛曾多次扮演救火奇兵

老叶评球
2024-05-01 08:47:30
大妈带着3000元退休金,去女儿家养老,被女婿拒绝:这不是钱的事

大妈带着3000元退休金,去女儿家养老,被女婿拒绝:这不是钱的事

飞翔侃情感
2024-04-29 10:34:47
2024-05-01 15:06:44
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 824关注度
往期回顾 全部

科技要闻

余承东卸任华为终端CEO 新任命为董事长

头条要闻

王毅和阿根廷外长会谈 阿方:奉行对华友好政策不改变

头条要闻

王毅和阿根廷外长会谈 阿方:奉行对华友好政策不改变

体育要闻

"意甲最佳"金玟哉 踢回了中超水平...

娱乐要闻

黄子韬被曝求婚徐艺洋 大量亲密照曝光

财经要闻

俞敏洪,踏足A股!

汽车要闻

预售2.89-3.49万 奔腾小马正式开启预售

态度原创

时尚
教育
家居
房产
公开课

中年女人就要这么打扮自己!初夏准备好这4件单品,更显年轻

教育要闻

2分钟后纠结的我就不纠结了!我用了假设法,瞬间不纠结

家居要闻

心之所栖 黑白灰色系打造设计专属感

房产要闻

单价2万内,装标4200+,主城改善大盘无套路硬刚!

公开课

父亲年龄越大孩子越不聪明?

无障碍浏览 进入关怀版