as-if-serail语义把单线程程序保护起来了,它可以保证在重排序的前提下程序的最终结果始终都是一致的。
 
其实对于上段代码,他们存在这样的happen-before关系:
  1. A happens-before B
  2. B happens-before C
  3. A happens-before C
1、2是程序顺序次序规则,3是传递性。但是,不是说通过重排序,B可能会排在A之前执行么,为何还会存在存在A happens-beforeB呢?这里再次申明A happens-before B不是A一定会在B之前执行,而是A的对B可见,但是相对于这个程序A的执行结果不需要对B可见,且他们重排序后不会影响结果,所以JMM不会认为这种重排序非法。
 
我们需要明白这点:在不改变程序执行结果的前提下,尽可能提高程序的运行效率。

重排序对多线程的影响

在单线程环境下由于as-if-serial语义,重排序无法影响最终的结果,但是对于多线程环境呢?
 
如下代码:
public class RecordExample {
    int a = 0;
    boolean flag = false;

    /**
     * A线程执行
     */
    public void writer(){
        a = 1;                  // 1
        flag = true;            // 2
    }

    /**
     * B线程执行
     */
    public void read(){
        if(flag){                  // 3
           int i = a + a;          // 4
        }
    }
}

A线程执行writer(),线程B执行read(),线程B在执行时能否读到 a = 1 呢?答案是不一定(注:X86CPU不支持写写重排序,如果是在x86上面操作,这个一定会是a=1)。

由于操作1 和操作2 之间没有数据依赖性,所以可以进行重排序处理,操作3 和操作4 之间也没有数据依赖性,他们亦可以进行重排序,但是操作3 和操作4 之间存在控制依赖性。假如操作1 和操作2 之间重排序:
按照这种执行顺序线程B肯定读不到线程A设置的a值,在这里多线程的语义就已经被重排序破坏了。
 
操作3 和操作4 之间也可以重排序。但是他们之间存在一个控制依赖的关系,因为只有操作3 成立操作4 才会执行。当代码中存在控制依赖性时,会影响指令序列的执行的并行度,所以编译器和处理器会采用猜测执行来克服控制依赖对并行度的影响。假如操作3 和操作4重排序了,操作4 先执行,则会先把计算结果临时保存到重排序缓冲中,当操作3 为真时才会将计算结果写入变量 i 中。
 
通过上面的分析, 重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义