第三章 Java記憶體模型之重排序
接上一章Java記憶體模型之基礎,我們接著探究Java記憶體模型。我們在上一章已經接觸過重排序了,但是還沒有那麼透徹,這章重點來說下一下重排序。
定義:
重排序是編譯器和處理器為了優化程式效能而對指令序列進行重新排序的一種手段。
我們需要理解這幾點:
1)資料依賴性
2)as-if-serial
3)程式順序規則
4)重排序對多執行緒的影響
資料依賴性:
如果兩個操作訪問同一個變數,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在資料依賴性。
名稱 | 程式碼例項 | 說明 |
---|---|---|
寫後讀 | a=1;b=a | 寫一個變數之後,再讀這個變數 |
寫後寫 | a=1;a=2 | 寫一個變數之後,再寫這個變數 |
讀後寫 | a=b;b=1 | 讀一個變數之後,再寫這個變數 |
上面的幾個情況,只要重排序都會影響最後的執行結果,只要兩個操作有寫的操作,重排序就會有影響。當編譯器和處理器進行重排序的時候會遵照資料依賴性,編譯器和處理器不會改變存在資料依賴性關係的兩個操作的執行順序。
資料依賴性僅僅針對單個處理器,不同處理器不予考慮,想管也管不動啊。
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的執行順序不會對執行結果有影響。

總結一下as-if-serial:
as-if-serial語義把單執行緒程式保護了起來,遵守as-if-serial的編譯器,runtime和處理器共同為編寫單執行緒程式的程式設計師一個幻覺(合著這麼多年被欺騙了啊):單執行緒程式是按照程式的順序來執行的。as-if-serail語義是單執行緒程式設計師無需擔心重排序干擾他們,也無需擔心記憶體的可見性問題。
程式順序規則:
這個在上一章就已經講過了,我們再來一下劃重點。
如果A happens-before B,實際B可以在A之前執行,JMM不一定要A一定要在B之前執行。僅僅是要求上一個操作(結果)對於下一個操作可見,且前一個操作按順序排在第二個操作之前。
這個例子A的執行結果不需要對操作B可見,而且重排序之後對執行結果無影響,這種情況下,JMM會認為重排序並不非法(not illegal),JMM允許這種重排序。因為計算機技術和硬體技術有著一個共同的目標,在不改變執行結果的前提下,儘可能提高並行效率。
重排序對多執行緒的影響
最後我們看一下重排序對多執行緒的影響。說了這麼多單執行緒的,我們來看一下多執行緒中的表現。
class demo{ int a = 0; boolean flag = false; public void writer(){ a = 1;//1 flag = true;//2 } public void reader(){ if(flag){//3 int i = a*a;//4 }
flag 是個標記,表示變數a是否被寫入。如果有兩個執行緒A和B,A首先執行writer()方法,隨後B執行reader()方法。在操作4的時候,能否看到執行緒A在操作1對共享變數a的寫入呢?
答案:不一定!!!
其實通過我們前面知識的理解,雖然單執行緒可以根據規則去判斷是否重排序,但是對多執行緒是無效的,我們從單執行緒的關係來看是允許進行重排序的,那麼就是說,1、2 和 3、4 的順序可以任意交換。而且1、2 和3、4的順序也是未知。
首先一種可能的情況 2 、3、 4、1。

1、2重排序
我們發現 a還沒被寫入,flag就被賦值了,1、2重排序,導致B執行緒的結果是錯誤的。這就是重排序把多執行緒程式的語義破壞了。
我們再看另一種情況3、4重排序。結果也是錯誤的。重排序把多執行緒程式的語義破壞了。編譯器和處理器會採用猜測(Speculation)執行來客服控制相關性對並行度的影響。以處理器的猜測執行為例子,執行執行緒B的處理器可以提前讀取並計算a*a,然後把這個計算結果臨時儲存到一個名為重排序緩衝(Reorder Buffer,ROB)的硬體快取中,當操作3為真,把計算結果寫入到變數中。

3、4重排序
也就是猜測執行實際上對操作3和操作4進行了重排序。
結論:
在單執行緒程式中,對存在控制依賴的操作重排序,不會改變執行結果(as-if-serial語義允許存在控制依賴的操作做重排序的原因)。但是在多執行緒程式中,對存在控制依賴的操作重排序,可能就會改變程式的執行結果。
換一句話就是 單執行緒中 對於控制依賴,編譯器和處理器已經解決了重排序的問題所以不會影響最後執行結果。
注意這兩種依賴:
1)資料依賴(在單執行緒重排序可能會影響執行結果,在多執行緒更是可以影響執行結果)
2)控制依賴(運用猜測、 快取 ,重排序 在單執行緒不影響執行結果,在多執行緒可能會影響執行結果 )