jvm之指令重排_java指令重排

jvm之指令重排_java指令重排引言:在Java中看似顺序的代码在JVM中,可能会出现编译器或者CPU对这些操作指令进行了重新排序;在特定情况下,指令重排将会给我们的程序带来不确定的结果…..1. 什么是指令重排?     在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

引言:在Java中看似顺序的代码在JVM中,可能会出现编译器或者CPU对这些操作指令进行了重新排序;在特定情况下,指令重排将会给我们的程序带来不确定的结果…..

1.  什么是指令重排?

      在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

 2.  数据依赖性

    主要指不同的程序指令之间的顺序是不允许进行交互的,即可称这些程序指令之间存在数据依赖性。

    主要的例子如下:

  1. 名称  代码示例    说明  
  2. 写后读     a = 1;b = a;    写一个变量之后,再读这个位置。  
  3. 写后写     a = 1;a = 2;    写一个变量之后,再写这个变量。  
  4. 读后写     a = b;b = 1;    读一个变量之后,再写这个变量。  
名称    代码示例    说明
写后读     a = 1;b = a;    写一个变量之后,再读这个位置。
写后写     a = 1;a = 2;    写一个变量之后,再写这个变量。
读后写     a = b;b = 1;    读一个变量之后,再写这个变量。

 进过分析,发现这里每组指令中都有写操作,这个写操作的位置是不允许变化的,否则将带来不一样的执行结果。

  编译器将不会对存在数据依赖性的程序指令进行重排,这里的依赖性仅仅指单线程情况下的数据依赖性;多线程并发情况下,此规则将失效。

3.  as-if-serial语义

   不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守as-if-serial语义。

   分析:  关键词是单线程情况下,必须遵守;其余的不遵守。

   代码示例:

  1. double pi  = 3.14;    //A  
  2. double r   = 1.0;     //B  
  3. double area = pi * r * r; //C  
double pi  = 3.14;    //A
double r   = 1.0;     //B
double area = pi * r * r; //C

 分析代码:   A->C  B->C;  A,B之间不存在依赖关系; 故在单线程情况下, A与B的指令顺序是可以重排的,C不允许重排,必须在A和B之后。

结论性的总结:

   as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器,runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

   核心点还是单线程,多线程情况下不遵守此原则。

4.  在多线程下的指令重排

     首先我们基于一段代码的示例来分析,在多线程情况下,重排是否有不同结果信息:

  1. class ReorderExample {  
  2. int a = 0;  
  3. boolean flag = false;  
  4.   
  5. public void writer() {  
  6.     a = 1;                   //1  
  7.     flag = true;             //2  
  8. }  
  9.   
  10. Public void reader() {  
  11.     if (flag) {                //3  
  12.         int i =  a * a;        //4  
  13.         ……  
  14.     }  
  15. }  
  16. }  
class ReorderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;                   //1
    flag = true;             //2
}

Public void reader() {
    if (flag) {                //3
        int i =  a * a;        //4
        ……
    }
}
}

上述的代码,在单线程情况下,执行结果是确定的, flag=true将被reader的方法体中看到,并正确的设置结果。 但是在多线程情况下,是否还是只有一个确定的结果呢?

假设有A和B两个线程同时来执行这个代码片段, 两个可能的执行流程如下:

    可能的流程1, 由于1和2语句之间没有数据依赖关系,故两者可以重排,在两个线程之间的可能顺序如下: 

  jvm之指令重排_java指令重排

   可能的流程2:, 在两个线程之间的语句执行顺序如下:

jvm之指令重排_java指令重排

  

    

根据happens- before的程序顺序规则,上面计算圆的面积的示例代码存在三个happens- before关系:

  1. A happens- before B;
  2. B happens- before C;
  3. A happens- before C;

这里的第3个happens- before关系,是根据happens- before的传递性推导出来的 

    在程序中,操作3和操作4存在控制依赖关系。当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行线程B的处理器可以提前读取并计算a*a,然后把计算结果临时保存到一个名为重排序缓冲(reorder buffer ROB)的硬件缓存中。当接下来操作3的条件判断为真时,就把该计算结果写入变量i中。从图中我们可以看出,猜测执行实质上对操作3和4做了重排序。重排序在这里破坏了多线程程序的语义。

     核心点是:两个线程之间在执行同一段代码之间的critical area,在不同的线程之间共享变量;由于执行顺序、CPU编译器对于程序指令的优化等造成了不确定的执行结果。

5.  指令重排的原因分析

    主要还是编译器以及CPU为了优化代码或者执行的效率而执行的优化操作;应用条件是单线程场景下,对于并发多线程场景下,指令重排会产生不确定的执行效果。

6.  如何防止指令重排

    volatile关键字可以保证变量的可见性,因为对volatile的操作都在Main Memory中,而Main Memory是被所有线程所共享的,这里的代价就是牺牲了性能,无法利用寄存器或Cache,因为它们都不是全局的,无法保证可见性,可能产生脏读。
    volatile还有一个作用就是局部阻止重排序的发生,对volatile变量的操作指令都不会被重排序,因为如果重排序,又可能产生可见性问题。
    在保证可见性方面,锁(包括显式锁、对象锁)以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同,例如同步锁保证得到锁时从内存里重新读入数据刷新缓存,释放锁时将数据写回内存以保数据可见,而volatile变量干脆都是读写内存。

7.  可见性

    这里提到的可见性是指前一条程序指令的执行结果,可以被后一条指令读到或者看到,称之为可见性。反之为不可见性。这里主要描述的是在多线程环境下,指令语句之间对于结果信息的读取即时性。

8.  参考文献

  •      http://www.infoq.com/cn/articles/java-memory-model-2
  •      http://www.cnblogs.com/chenyangyao/p/5269622.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/183859.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Hibernate知识点总结

    Hibernate知识点总结

    2021年11月29日
  • 移动手机app开发

    移动手机app开发App开发,是指专注于手机应用软件开发与服务。App是application的缩写,通常专指手机上的应用软件,或称手机客户端。另外目前有很多在线app开发[1]平台,比如应用之星平台很好用。App开发,是指专注于手机应用软件开发与服务。App是application的缩写,通常专指手机上的应用软件,或称手机客户端。苹果公司的Appstore开创了手机软件业发展的新篇章,使得第三方软

  • 在ubuntu下完美安装RTX(解决离线、乱码问题) 转

    在ubuntu下完美安装RTX(解决离线、乱码问题) 转

  • Qt使用多线程的一些心得——1.继承QThread的多线程使用方法

    Qt使用多线程的一些心得——1.继承QThread的多线程使用方法摘要Qt多线程方法1继承QThread1写一个继承于QThread的线程2QThread的几个函数quitexitterminate函数3正确的终止一个线程4如何正确启动一个线程41正确的启动一个全局线程和UI一直存在的线程42如何启动一个局部线程用完即释放的线程继承QThread的一些总结使用QObject实现多线的方法见:http://blog.csdn.net/cz

  • 用python写一个简单的贪吃蛇游戏(附代码)「建议收藏」

    用python写一个简单的贪吃蛇游戏(附代码)「建议收藏」今天要给大家分享的是一个pygame写的“贪吃蛇”小游戏

  • 查看Linux系统版本内核命令大全

    查看Linux系统版本内核命令大全目录命令一:查看当前系统发行版本详细信息命令二:查看当前系统内核信息命令三:查看当前系统版本信息命令四:查看CPU相关信息命令五:查看系统位数Linux系统内核、发行版本有很多,那么如何查看当前Linux系统的内核信息、Linux系统发行版本等信息呢?Linux百科网分享查询Linux系统详细信息的方法:命令一:查看当前系统发行版本详细信息LSB是LinuxStandardBase的缩写,lsb_release命令用来显示LSB和特定版本的相关信息执行命令:lsb_

    2022年10月12日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号