首先说明本文并不是讲解volatile不保证原子性、如何保证可见性xxxx,还不懂的请参考
让你彻底理解volatile
并发关键字volatile(重排序和内存屏障)

本文针对以下两个问题解答

1. 重排序规则中,volatile读写跟普通读写有什么关系,为什么要限制它们
2. volatile读操作的内存屏障的LoadLoad屏障到底是在读前还是读后

直接进入主题

重排序规则中,volatile读写跟普通读写有什么关系,为什么要限制它们


volatile变量的读写不能重排序很好理解,可是这跟普通读写有什么关系呢?普通读写是怎么影响到结果的?

首先明确volatile读/写的内存语义:

  • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。(注意:是所有的共享变量,不光是volatile变量)
  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存 (注意:这里也是所有的共享变量)

即:无论是volatile读还是volatile写,都会刷新本地内存的所有共享变量,而不单是volatile变量

保证了在读volatile变量之前,内存中所有的共享变量都是最新的,也就是之前执行的的任意线程的写操作都对本次的读操作可见;
同样,本次写操作都对之后任意线程执行的读操作可见。

可能有读者觉得这样没什么必要,那我们来举例看看不这么限制的话会有什么后果

volatile int a = 0;
int b = 1;public void A (){b = 2;    // 1 普通写a = 1;      // 2 volatile写
}public void B() {int c = 0;if (a == 1)    // 3  volatile读c = b;      // 4 普通写
}

假如volatile不影响重排序,那么由于代码1和代码2两处没有数据依赖性,所以二者是可以重排序的。
我们假设代码2在代码1之前被执行,此时由于a是volatile变量,所以将a = 1, b = 1刷新进入主内存;
如果这时候方法A所在的线程cpu时间片用完了,轮到了方法B在另一个线程中执行,由于a是volatile变量所以代码3处执行的时候会将b = 1, a = 1从主内存中读出,此时代码4再执行的话c会变为1,而不是预想的2(因为按照我们书写的顺序来看,a=1发生在b=2之后)

发生这种错误的原因在于:volatile变量写操作与在其之前的代码发生了重排序,使得刷新内存的时机提早了,可能会漏掉我们写在volatile变量赋值操作之前的那些共享变量的修改。

所以这就引出了volatile变量对指令重排序的第一个影响:

  • 第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。确保volatile写操作刷新内存里共享变量的值时,程序员希望发生的变动都能够正确的刷新到内存中

而这例子也对应:
第一个操作为普通读/写,第二个操作为volatile写,也验证了确实是不能重排序,因为是可能会产生影响的。

PS:笔者建议此处应该按第二个操作去记忆

同理,再举个例子解释,第一个操作为volatile读,第二个操作为普通读/写 不能重排原因。

首先要明确happens-before中的猜测执行机制
当存在控制依赖时,编译器和处理器会采取猜测执行机制来提高并行度

int a = 1;
boolean flag = true ;
if(flag){ //代码5a * = 2; //代码6
}

代码5和6不存在数据依赖,可能会重排,处理器和编译器会先将代码6的执行结果放在缓冲区,等代码5判断为真时之后,将缓冲区的结果直接赋值给a,即实质上对操作5和6做了重排序

那么继续用上述例子,稍作修改

volatile int a = 1;
int b = 1;public void A (){a = 2; //1 volatile写
}public void B() {int c = 0;if (a == 1)    // 3  volatile读c = b;      // 4 普通写
}

现在换成先执行方法B。

基于上述的猜测执行机制,程序会把b=1先写到缓冲区,
如果这时候方法B所在的线程cpu时间片用完了,轮到了方法A在另一个线程中执行,把a赋值为2,然后返回方法B所在的线程,可是此时不满足判断条件,所以c不会被赋值为1,即c的值还是为0,而这显然是不对的。

同理,引出第二条规则:

  • 第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。确保volatile读操作读取内存里的最新值是程序员希望读到的、操作的值

到此,规则中跟普通读写相关的“NO”已经解释完了

而volatile变量的读写间不能重排序就不举例了。

volatile读操作的内存屏障的LoadLoad屏障到底是在读前还是读后

在说volatile读之前,我们根据规则表,先来推volatile写的内存屏障

  1. 第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。所以在写操作前插入StoreStore屏障
  2. 第一个操作是volatile写时,第二个操作的volatile读/写操作都不能重排序。所以在写操作后插入StoreLoad屏障

好的,符合,那我们现在来推volatile读操作的内存屏障

第一个操作是volatile读操作时,不管第二个操作是什么,都不能重排序。而第二个操作则包括了读、写(不管是普通读写还是volatile读写)。

  • 所以对于后续的读操作,则应在volatile读后插入LoadLoad屏障
  • 对于后续的写操作,则应在volatile读后插入LoadStore屏障

其实到这里,就已经可以了,但我们可以反推否定是读前插入:因为第二个操作为volatile读时,对于普通读/写,是没有限制“NO”的,如图

正推跟反推,都证明了,LoadLoad是在读后插入的

本文到此完,吐槽一下笔者查了好多资料,居然没有相关文章,因此整理出来,希望能给各位一点帮助。

有误欢迎指出。

参考文章:
Java并发学习笔记 – Java中的Lock、volatile、同步关键字

volatile的指令重排序理解

指令重排序

volatile禁止重排序详解相关推荐

  1. 利用结构体数组实现重排序(详解)

    一:要求 输入乱序的 成绩单 包括 姓名和成绩,成绩按照递增顺序 输出,如果遇见成绩相同的 按名字的字典序输出. 示例: 输入 5 w 12 a 12 v 7 c 3 e 9 输出: c 3 v 7 ...

  2. ConcurrentProgramming:volatile/构造方法溢出/禁止重排序

    ConcurrentProgramming:volatile/构造方法溢出/禁止重排序 关键词 内存可见性原理(直接操作主存) 禁止指令重排原理(内存屏障,最终目的:直接操作主内存) 对volatil ...

  3. pandas dataframe中的列进行重新排序、倒排、正排、自定义排序详解及实践

    pandas dataframe中的列进行重新排序,pandas dataframe列重排.倒排.正排.自定义排序详解及实践 实施数据构建: import pandas as pd import nu ...

  4. 希尔排序基础java代码_java 算法之希尔排序详解及实现代码

    摘要:这篇Java开发技术栏目下的"java 算法之希尔排序详解及实现代码",介绍的技术点是"希尔排序详解.实现代码.希尔排序.Java.实现.代码",希望对大 ...

  5. Linux 中使用 sort 指令分组排序详解

    Linux 中使用 sort 指令分组排序详解 sort 中进行分组排序主要用到的选项为 -k,此文,我们着重于该选项的使用方式,用到的其它选项不做解释,有兴趣的同学可以查看帮助文档 1. 数据准备 ...

  6. (*(volatile unsigned int *))详解

    (*(volatile un 从日剧看日本单身狗现状 登录注册 阿拉丁神丢的博客 http://blog.sina.com.cn/ddlovetechnology [订阅][手机订阅] 首页博文目录图 ...

  7. 地理坐标系、大地坐标系与地图投影与重投影详解

    地理坐标系.大地坐标系与地图投影与重投影详解 基本概念 首先简单介绍一下地理坐标系.大地坐标系以及地图投影的概念: 地理坐标系:为球面坐标. 参考平面地是椭球面,坐标单位:经纬度: 投影坐标系:为平面 ...

  8. c语言排序常用问题,【更新中】【排序详解】解决排序问题(以C语言为例)

    [更新中][排序详解]解决排序问题(以C语言为例) [更新中][排序详解]解决排序问题(以C语言为例) 文章目录 排序的相关概念 简单排序 一.插入排序: (一)插入排序基本思想 (二)插入排序基本操 ...

  9. 八大排序 详解(下)——指向函数的指针 的使用

    <八大排序 详解(上)> <八大排序 详解(中)> 紧接前两篇博文,我们这篇博文所要讲解的是我们前两篇博文编写的所有函数的使用.生成随机数组的函数的讲解以及一种及其凶悍的调用方 ...

最新文章

  1. .NET 3.5 中WCF客户端代理性能改进以及最佳实践
  2. 什么叫n+1次select查询问题?
  3. ASP.NET Core 对Controller进行单元测试
  4. from mysql partition select_爬虫(九十九)mysql详解二
  5. 7个良心到难以置信的自媒体免费网站推荐
  6. wordpress邮件地址混淆 你没权限访问整个邮件地址造成的死链接
  7. BZOJ 3097: Hash Killer I【构造题,思维题】
  8. Linux运维工程师必备工具合集
  9. 进阶项目(6)LCD12864液晶屏幕设计讲解
  10. 功能安全标准-ISO26262-1---使用范围和主要内容
  11. 1.3【展讯平台】Android 驱动(Kernel)、系统(framework) 定制,调试日志
  12. mac 命令行 解压7z文件_命令行压缩解压7z
  13. python 圆形检测_基于opencv-python的圆形检测
  14. World Cup Betting(PAT)
  15. 【工作效率提升】Search Everything 下载安装
  16. python中间值怎么获得_使用python 3的中间值
  17. 安卓开发之使用第三方的聚合数据API,QQ测吉凶案、身份证号码查询。
  18. 【Cocos笔记1】摇杆,移动,转向
  19. php 条码校验位,Code128条形码如何计算其宽度?如何得出其校验位?
  20. 单调队列-原理详解(deque实现)

热门文章

  1. 学习各种计算机知识网站推荐
  2. 使用Markdown语言在博客的文章中添加视频
  3. 电子学会青少年等级考试C语言(二级)
  4. 微信小程序优惠券样式
  5. python爬虫_爬取京东商品写入Excel表
  6. C语言连续自动自加怎么表示,为什么不建议在C语言中连续使用自增自减运算符...
  7. ubuntu有怪怪的声音一直跟读屏幕解决方法
  8. 关于c3p0报错:An attempt by a client to checkout a Connection has timed out.
  9. 慧荣SM3267AB主控U盘量产的工具
  10. 自学Python问题记录2:解决画风玫瑰图出现报错No artists with labels found to put in legend.