温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JMM指令重排序的示例分析

发布时间:2021-09-05 18:35:40 来源:亿速云 阅读:415 作者:小新 栏目:编程语言

这篇文章给大家分享的是有关JMM指令重排序的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

一、指令为什么要重排序?

在计算机系统中,指令为了更快的完成结果,会根据逻辑关系、指令大小进行重排序,以达到超流水线的效果,但在代码执行完后,保证结果输出是一致的。

在JVM中又两条原则:

  • as-if -serials         这个规则适用于单线程,默认自动处理

  • happens-before   这个规则适用于多线程,当然也要通过相应的锁和关键字来实现

二、JMM(Java Memory Manager)指令重排序如何实现的呢?

我们知道,JIT会根据CPU架构编译合适的代码去执行,因此,在汇编层面讨论反而不见得能统一意见,比如X86,他只有lock来实现,其他CPU架构则是LoadLoad,StoreStore,LoadStore,StoreLoad

实际上我们还是在JIT层面思考比较容易理解,这里我们定义一个类

(注意:这个类未必和JIT实际效果一致,取决于JIT激进程度)

package com.apptest;

public class VolatileWatcher {

    private String A;
    private String B;
    private String C;
    private String D;
    private String E;

    public void run() {
        String a = "a";
        String b = "b";
        String c = "c";
        String d = "d";
        String e = "e";
        A = a;
        B = b;
        C = c;
        D = d;
        E = e;

        System.out.println(A);
        System.out.println(B);
        System.out.println(C);
        System.out.println(D);
        System.out.println(E);
    }
}

那么如果重排序,效果可能如下

package com.apptest;

public class VolatileWatcher {

    private String A;
    private String B;
    private String C;
    private String D;
    private String E;

 
    public void run() {
        String a = "a";
        String b = "b";
        String c = "c";
        String d = "d";
        String e = "e";

        A = a;
        System.out.println(A);

        B = b;
        System.out.println(B);

        C = c;
        System.out.println(C);
        D = d;
        System.out.println(D);

        E = e;
        System.out.println(E);
    }
}

这是JIT可能重排序后的结果,实际上寄存器、CPU也会进行重排序,但最终也会保证内存一致性。

三、如何禁止指令重排序呢?

Java中,禁止指令重排序的关键字只有2个,一个是volatile、另一个是final,某种成都上,也能说明final和volatile不能同时修饰一个变量的原因,因为他们的功能有些重叠。

3 .1  要不要禁止

首先要确定要不要禁止重排序,重排序往往在多线程中出现问题,如果程序在串行执行,完全没有必要,因此,单线程中的引用类型(不包括常量和字符串常量,这些类型引用建议加final)建议不要加final和volatile,当然,final需要考虑深入一些,因为有时我们需要做一些强约束,但总体来说能不加就不加。

3.2 原理

多线程中,维护hanpens-before原则不是JVM自身就能处理的,还需要在代码层面进行相应的指示,此外,相应的JIT会将指示编译成指令,让寄存器和CPU也遵守。

指令重排序的达到的最终效果

读操作

JMM指令重排序的示例分析

写操作

JMM指令重排序的示例分析

3.3 效果演示

回到文章开头,我们给VolatileWatcher的C变量添加volatile修饰,那么JIT重排序后的结果可能是如下情况

public class VolatileWatcher {

    private String A;
    private String B;
    private volatile String C;
    private String D;
    private String E;

    public void run() {
        String a = "a";
        String b = "b";
        String c = "c";
        String d = "d";
        String e = "e";
        B = b;
        A = a;
        C = c; //写屏障
        E = e;
        D = d;

        System.out.println(A);
        System.out.println(B);
        System.out.println(C); //读屏障
        System.out.println(D);
        System.out.println(E);
    }
}

我们发现,A、B不能跨越C变量的写屏障往下重排序,但是屏障上方的A、B之间也是可以重排序的,E、D不能跨越读屏障,但是E、D可以在读屏障和写屏障之间排序。(这里故意写成A,B,E,D在屏障外顺序变化,目的主要是为了说明屏障的作用,具体看实际效果)

四、final和volatile的区别?

我们知道,final修饰的是只读变量,有很强的“只读”约束性,所有final修饰的变量都需要在构造方法中初始化(类外部final字段的最终会插入到super(...)之后 ),而volatile一般用于多线程变量的读写,但他们都具备实现内存屏障的能力,可以说,都能实现禁止重排序。

不同点:

final字段的写操作,只作用于构造函数。

final字段的读操作,读取和volatile基本一样

感谢各位的阅读!关于“JMM指令重排序的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jmm
AI