天天看點

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是可見的。