Java併發程式設計系列之五:happens-before原則
前言
happens-before是JMM的核心,之所以設計happens-before,主要出於以下兩個方面的因素考慮的:1)程式設計師的角度,JMM記憶體模型需要易於理解、易於程式設計;2)編譯器和處理器的角度,編譯器和處理器希望記憶體模型對其束縛越少越好,這樣就可以根據自己的處理規則進行優化。但是這兩個方面其實是相互矛盾的,因為JMM易於程式設計和理解就意味著對編譯器和處理器的束縛就越多。
happens-before定義
基於上面的考慮,設計JMM時採用了一種折中的選擇——JMM將需要禁止的重排序分為兩類(因為編譯器和處理器的優化大部分是重排序,所以JMM的處理的關鍵也就是重排序了):
- 會改變程式執行結果的重排序
- 不會改變程式執行結果的重排序
對應這兩種情況,JMM採用了不同的策略:
- 對於會改變程式執行結果的重排序,JMM要求編譯器和處理器必須禁止這種重排序
- 對於不會改變程式執行結果的重排序,JMM對編譯器和和處理器不做任何要求(自然,編譯器和處理器可以其進行重排序)
所以JMM的設計基於這樣一種原則:先保證正確性,在考慮執行效率問題。
說了這麼多,與happens-before原則有什麼關係呢?從上面可以看到JMM實際上可以看做是操作之間的約束模型,這種約束模型的實現就是我們要提到的happens-before了。happens-before**用來指定兩個操作之間的執行順序**,這兩個操作可以在一個執行緒之內也可以在不同的執行緒中,所以這種對操作順序的關係的界定可以為程式設計師提供記憶體可見性的保證。具體happen-before的定義如下:
1)如果一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前
2)兩個操作之間存在happens-before關係,並不意味著Java平臺的具體實現必須要按照happens-before關係指定的順序來執行。
上面第二句話的意思就是說,如果重排序之後的執行結果與按照原來那種happens-before關係執行的結果一致,那麼JMM允許編譯器和處理器進行這種重排序。所以可以認為:只要不改變程式的執行結果,編譯器和處理器可以隨意優化。聯絡之前提到的as-if-serial語義(保證單執行緒內的程式執行結果不會改變),現在提到的happens-before則保證正確同步的多執行緒的執行程式的執行結果不會發生改變。
happens-before規則
總共有六條規則:
- 程式順序規則:一個執行緒中的每個操作,happens-before於隨後該執行緒中的任意後續操作
- 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的獲取
- volatile變數規則:對一個volatile域的寫,happens-before於對這個變數的讀
- 傳遞性:如果A happens-before B,B happens-before C,那麼A happens-before C
- start規則:如果執行緒A執行執行緒B的start方法,那麼執行緒A的ThreadB.start()happens-before於執行緒B的任意操作
- join規則:如果執行緒A執行執行緒B的join方法,那麼執行緒B的任意操作happens-before於執行緒A從TreadB.join()方法成功返回。
看看start規則,假設這樣一種情況,如果執行緒A在執行執行緒B的start方法之前修改了一些共享變數的值,那麼當執行緒B執行start方法的時候,會去讀取這些修改的共享變數的值(上面規則就是這麼規定的),這就意味著執行緒A對共享變數的修改對執行緒B可見
下面看看join規則是怎麼回事。join方法的本義是等待當前執行的執行緒終止。假設線上程B終止之前,修改了一些共享變數(完全可能啊),執行緒A從執行緒B的join方法成功返回後,就會讀取這些修改的共享變數。這樣也保證了執行緒B對共享變數的修改對執行緒A是可見的。