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

数据依赖

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

名称

代码示例

说明

写后读

a=1;b=a;

写一个变量之后,再读这个位置

写后写

a=1;a=2;

写一个变量之后,再写这个变量

读后写

a=b;b=1;

读一个变量之后,再写这个变量



as-if-serial语义

        as-if-serial语义的意思是:不管怎么重排序程序的执行结果不能被改变。编译器、runtime和处理器必须都遵守as-if-serial语义。如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重新排序。下面是计算圆面积的示例。

double pi = 3.14;       //A
double r = 1.0;          //B
double area = pi * r;  //C



程序顺序规则

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

1) A happens-before B

2) B happens-before C

3) A happens-before C


        


重排序对多线程的影响

         重排序是否会改变多线程程序的执行结果。

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变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入吗?

        不一定能看到。

         由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序,同样3和4没有数据依赖关系,编译器也可以对这两个操作重排序。

         操作1和操作2做了重排序,线程A首先标记变量flag,随后B线程读取这个变量,由于条件为true,线程B读取变量a,此时变量a还没有写入,这里多线程程序的语义被破坏了。

        操作3和操作4存在控制依赖关系。代码存在控制依赖关系时,会影响指令程序的并行度。

 

JSR-133: JavaTM Memory Model and Thread Specification
JSR 133 (Java Memory Model) FAQ: