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

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