第三章 Java内存模型之重排序

作者: 猪_队友 | 来源:发表于2018-11-14 19:29 被阅读27次

    接上一章Java内存模型之基础,我们接着探究Java内存模型。我们在上一章已经接触过重排序了,但是还没有那么透彻,这章重点来说下一下重排序。

    定义:
    重排序是编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

    我们需要理解这几点:
    1)数据依赖性
    2)as-if-serial
    3)程序顺序规则
    4)重排序对多线程的影响

    数据依赖性:

    如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。

    名称 代码实例 说明
    写后读 a=1;b=a 写一个变量之后,再读这个变量
    写后写 a=1;a=2 写一个变量之后,再写这个变量
    读后写 a=b;b=1 读一个变量之后,再写这个变量

    上面的几个情况,只要重排序都会影响最后的执行结果,只要两个操作有写的操作,重排序就会有影响。当编译器和处理器进行重排序的时候会遵照数据依赖性,编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序。

    数据依赖性仅仅针对单个处理器,不同处理器不予考虑,想管也管不动啊。

    as-if-serial语句:

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

    为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序。因为重排序会改变执行的结果。但是如果操作之间不存在数据依赖关系,那么这些操作就可能被重排序。

    int a = 1;//A
    int b = 2;//B
    int c = a+b;//C
    

    依赖关系如下



    当改变 A、B的执行顺序不会对执行结果有影响。


    总结一下as-if-serial:
    as-if-serial语义把单线程程序保护了起来,遵守as-if-serial的编译器,runtime和处理器共同为编写单线程程序的程序员一个幻觉(合着这么多年被欺骗了啊):单线程程序是按照程序的顺序来执行的。as-if-serail语义是单线程程序员无需担心重排序干扰他们,也无需担心内存的可见性问题。

    程序顺序规则:
    这个在上一章就已经讲过了,我们再来一下划重点。
    如果A happens-before B,实际B可以在A之前执行,JMM不一定要A一定要在B之前执行。仅仅是要求上一个操作(结果)对于下一个操作可见,且前一个操作按顺序排在第二个操作之前。

    这个例子A的执行结果不需要对操作B可见,而且重排序之后对执行结果无影响,这种情况下,JMM会认为重排序并不非法(not illegal),JMM允许这种重排序。因为计算机技术和硬件技术有着一个共同的目标,在不改变执行结果的前提下,尽可能提高并行效率。

    重排序对多线程的影响

    最后我们看一下重排序对多线程的影响。说了这么多单线程的,我们来看一下多线程中的表现。

    class demo{
    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 是个标记,表示变量a是否被写入。如果有两个线程A和B,A首先执行writer()方法,随后B执行reader()方法。在操作4的时候,能否看到线程A在操作1对共享变量a的写入呢?
    答案:不一定!!!
    其实通过我们前面知识的理解,虽然单线程可以根据规则去判断是否重排序,但是对多线程是无效的,我们从单线程的关系来看是允许进行重排序的,那么就是说,1、2 和 3、4 的顺序可以任意交换。而且1、2 和3、4的顺序也是未知。
    首先一种可能的情况 2 、3、 4、1。


    1、2重排序

    我们发现 a还没被写入,flag就被赋值了,1、2重排序,导致B线程的结果是错误的。这就是重排序把多线程程序的语义破坏了。

    我们再看另一种情况3、4重排序。结果也是错误的。重排序把多线程程序的语义破坏了。编译器和处理器会采用猜测(Speculation)执行来客服控制相关性对并行度的影响。以处理器的猜测执行为例子,执行线程B的处理器可以提前读取并计算a*a,然后把这个计算结果临时保存到一个名为重排序缓冲(Reorder Buffer,ROB)的硬件缓存中,当操作3为真,把计算结果写入到变量中。

    3、4重排序

    也就是猜测执行实际上对操作3和操作4进行了重排序。

    结论:
    在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(as-if-serial语义允许存在控制依赖的操作做重排序的原因)。但是在多线程程序中,对存在控制依赖的操作重排序,可能就会改变程序的执行结果。

    换一句话就是 单线程中 对于控制依赖,编译器和处理器已经解决了重排序的问题所以不会影响最后执行结果。

    注意这两种依赖:
    1)数据依赖(在单线程重排序可能会影响执行结果,在多线程更是可以影响执行结果)
    2)控制依赖(运用猜测、 缓存 ,重排序 在单线程不影响执行结果,在多线程可能会影响执行结果 )

    相关文章

      网友评论

        本文标题:第三章 Java内存模型之重排序

        本文链接:https://www.haomeiwen.com/subject/savjfqtx.html