1. 程式人生 > >JVM記憶體模型之重排序

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