重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段
- 在程序执行时,为了提高性能,编译器和处理器通常会对指令进行重排序,但是不能随意的重排序,必须满足一下两点
- 单线程情况下,不能改变程序执行结果
- 存在数据依赖性关系的操作之间不能重排序
数据的依赖性
- 如果两个操作访问同一个共享变量,且这两个操作有一个是写操作,那么这两个操作之间就存在数据依赖性
- 编译器和处理器会在重排序时,遵守数据依赖性,编译器和处理器不会改变存在数据依赖性的两个操作的执行顺序
- 这里的数据依赖性也只是针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑
as-if-serial语义
- as-if-serial语义的意思是,不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果不能改变。编译器、runtime和处理器都必须遵守as-if-serial语义
- 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖性的操作重排序,因为一旦重排序,就可能会影响程序的执行结果,对于不存在数据依赖性的操作,是可能会被编译器和处理器做重排序处理
int a = 1; //A
int b = 2; //B
int c = a + b; //C
- 上面的数据依赖性关系是,C依赖于A和B,而A和B之间不存在数据依赖性,因此编译器可以对A和B进行重排序
- as-if-serial语义给我们一个错觉,遵守as-if-serial语义的编译器和处理器,在单线程的情况下,程序是按照顺序执行,其实,对于不存在数据依赖性的操作,编译器和处理器可以随意的对其进行重排序操作,以提高执行效率
程序顺序规则
- 根据happens-before的程序规则,上面的例子中
- A happens-before B
- B happens-before C
- A happens-before C
- 这里的A happens-before C是根据happens-before的传递性推导出来的
重排序对多线程的影响
- 如下代码块
class Example {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
public void reader() {
if (flaf) { // 3
int i = a + a; // 4
}
}
}
- falg变量是一个标记,用来标记变量a是否已被写入,假如有线程A和线程B,A先执行writer,B执行reader,线程B在执行操作4时, 能否看到线程A在操作1对共享变量a的写入呢?答案是不一定能看到,由于1和2之间没有数据依赖性,编译器和处理器可以对这两个操作进行重排序,二3和4之间没有数据依赖性,编译器和处理器也可以对这两个操作进行重排序,当1和2进行重排序时,线程B执行的结果就处于一种待定的情况了
参考
周志明 《深入理解Java虚拟机》
方腾飞 《Java并发编程的艺术》
写在最后
我从未感觉如此强烈,我的灵魂离我如此遥远,而且的存在却如此的真实