重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
数据依赖性
定义:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。
数据依赖分为三种类型:写后读、写后写和读后写。这三种类型,只要重排序两个操作间的执行顺序,程序的执行结果就会被改变。
编译器和处理器在做重排序时,会遵守数据依赖性,也就是说编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器所考虑。
as-if-serial语义
as-if-serial语义的意思是:不管怎么重排序,(单线程)程序的执行结果不能被改变。编译器、runtime和处理器必须遵守as-if-serial语义。
为了遵守as-if-serial语义、编译器和处理器不会对存在数据依赖关系的操作做重排序。如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
如下示例:
double pi = 3.14; //A
double r = 1.0; //B
double area = pi*r*r; //C
A与C和B与C之间存在数据依赖关系,不能重排序;A和B不存在数据依赖关系,所以重排序不会改变结果。
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创造了一个幻觉:单线程程序是按程序的顺序来执行的。
程序顺序规则
根据happens-before的程序顺序规则来说,上述的A和B重排序是违反了这种规则,但是重排序后的结果与没有重排序的结果一致。所以,在这个情况下,JMM会认为这种重排序并不非法,JMM允许这种重排序。
在《Java内存模型基础》中提到过happens-before规则。
在计算机中,软件技术和硬件技术都有一个共同目标:在不改变程序执行结果的前提下,尽可能提高并行度。
重排序对多线程的影响
先上结论:在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果,但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。
控制依赖关系是指if语句的判断语句和语句内的内容重排序,当代码中存在控制依赖性时,会影响指令序列执行的并行度。
所以,有控制依赖的两个操作可以重排序,编译器和处理器会采用猜测(Speculation)执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例,执行语句体中的线程的处理器可以提前计算结果,并把结果临时保存到一个名为重排序缓冲(Reorder Buffer,ROB)的硬件缓存中,如果判断语句为真,就处理语句体中的结果。
参考
《Java并发编程的艺术》