JVM内存模型之重排序

为什么有重排序?

执行程序时,为了提高性能,处理器和编译器常常会对指令进行重排序。我们常将的重排序包括处理器重排序和编译器重排序。

 

重排序需要满足的条件

①在单线程环境下不能改变程序运行的结果;

②存在数据依赖关系的不允许重排序

如果知道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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章