为什么有重排序?
执行程序时,为了提高性能,处理器和编译器常常会对指令进行重排序。我们常将的重排序包括处理器重排序和编译器重排序。
重排序需要满足的条件
①在单线程环境下不能改变程序运行的结果;
②存在数据依赖关系的不允许重排序
如果知道happens-before规则,我们还可以把两个条件总结如下:如果两个操作不满足happens-before规则,JMM就可以对这两个操作重排序。
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
A、B、C三个操作存在如下关系:A、B不存在数据依赖关系,A和C、B和C存在数据依赖关系,因此在进行重排序的时候,A、B可以随意排序,但是必须位于C的前面,执行顺序可以是A –> B –> C或者B –> A –> C。但是无论是何种执行顺序最终的结果C总是等于3。
其实对于上段代码,他们存在这样的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不会认为这种重排序非法。
重排序对多线程的影响
重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义。
有如下代码。
public class RecordExample2 {
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 呢?答案是不一定。
由于操作1 和操作2 之间没有数据依赖性,所以可以进行重排序处理,操作3 和操作4 之间也没有数据依赖性,他们亦可以进行重排序,但是操作3 和操作4 之间存在控制依赖性。
假如操作1 和操作2 之间重排序。
按照这种执行顺序线程B肯定读不到线程A设置的a值,在这里多线程的语义就已经被重排序破坏了。
如果要解决上面的问题,我们可以用volatile关键字修饰flag,这样可以防止操作2被重排序。
本文转改自以下文章:
http://cmsblogs.com/?p=2116