zoukankan      html  css  js  c++  java
  • Java并发拾遗(二)——重排序

    一、 as-if-serial语义

    上篇文章中说道,编译器,运行时的JIT编译器或处理器都会对指令进行重排序以提升程序的执行性能。但这些重排序需要满足as-if-serial语义,不能随便的进行重排序。as-if-serial语义即指:不管怎么重排序,单线程程序的执行结果不能被改变。因此,不能对存在数据依赖关系的操作进行重排序。举个栗子:

        double pi = 3.14;
        double r = 1.0;
        double area = pi * r * r;  

    在上面的三个操作中,area变量依赖于 pi 与 r,因此在进行重排序时,pi 与 r 哪个操作先被执行是不确定的,可被重排序的,但是area就不能被重排序,其必须等到pi 与 r的操作完成才能开始执行。这种执行顺序是由编译器、runtime与处理器的重排序机制来保证的,我们可以放心的依赖这种规则。但是,请注意,说这么一大堆的前提是单线程程序,多线程引起之间的顺序错乱是并发引起的,而不是重排序。

    上篇文章中,说道Java内置的happens-before规则有一条说:单线程程序,前面的代码 happens-before 后面的代码。但这种说法是说对存在依赖关系的两个操作来讲的,对不存在依赖关系的操作(如两个操作操作不同变量),Java是允许其进行重排序的,因为这可以使程序执行的更快。

    二、重排序对多线程的影响

    上面所说内容,都是在单线程的前提下进行阐述的,下面来看重排序对多线程的影响。看个栗子:

        private class Test {
            int a = 0;
            boolean flag = false;
    
            public void write() {
                a = 1;
                flag = true;
            }
    
            public int read() {
                if(flag) {
                    int i = a;
                }
                
                // ** 
            }
        }

    如上的代码,flag的初衷是个标记量,用来表示变量a是否被写入了,a写入后才更改flag。然而在多线程的环境下,这种初衷就可能被重排序破坏,从而无法得到保证。这种破坏,提现在两处:

    1. 在write()方法内对不存在依赖关系的两条指令重排序

    由于在write()方法内的两条操作不存在依赖关系,因而可能被重排序,导致如下图的执行顺序

    即标志位flag先被置为TRUE,而a还没被赋值,然后线程B就开始执行了。

    2. 对read()方法内存在控制依赖性的语句进行猜测执行导致的重排序

    为了提升并行度,加快执行,在实际的执行过程中,编译器或处理器在执行read()方法时可能采取猜测执行的策略来执行。即先把a读进来,然后再判断flag,flag = true时就把预读的a赋值给i,flag = false时就丢弃a。这种对控制依赖性的猜测执行,实际上对read()方法内的语句进行了重排序。

    从以上两点,可知在多线程环境下,指令的重排序对于最终的程序结果会产生影响,因而在多线程并发的环境下,需要对重排序进行控制,在一些会影响结果的地方,禁止重排序。

  • 相关阅读:
    USB描述符(转)
    (转)Linux设备驱动之HID驱动 源码分析
    Linux USB 鼠标驱动程序详解(转)
    (转)linux如何获取鼠标相对位置信息
    从零写一个编译器(四):语法分析之构造有限状态自动机
    从零写一个编译器(三):语法分析之几个基础数据结构
    从零写一个编译器(二):语法分析之前置知识
    从零写一个编译器(一):输入系统和词法分析
    自底向上语法分析
    递归下降和LL(1)语法分析
  • 原文地址:https://www.cnblogs.com/dosmile/p/6725636.html
Copyright © 2011-2022 走看看