Happens-Before規則

1. 前言

從 JDK 5開始,Java 使用新的 JSR-133 內存模型,使用 happens-before 的概念來闡述操做間的可見性。java

2. 定義

JSR-133 對Happens-Before 的定義:程序員

Happens-Before Relationship Two actions can be ordered by a happens-before relationship. If one action > happens-before another, then the first is visible to and ordered before the second. It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java platform implementation. The happens-before relation mostly stresses orderings between two actions that conflict with each other, and defines when data races take place. There are a number of ways to induce a happens-before ordering, including:編程

  • Each action in a thread happens-before every subsequent action in that thread.
  • An unlock on a monitor happens-before every subsequent lock on that monitor.
  • A write to a volatile field happens-before every subsequent read of that volatile.
  • A call to start() on a thread happens-before any actions in the started thread.
  • All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
  • If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.

定義: 若是一個操做happens-before另外一個操做,那麼意味着第一個操做的結果對第二個操做可見,並且第一個操做的執行順序將排在第二個操做的前面。 兩個操做之間存在happens-before關係,並不意味着Java平臺的具體實現必須按照happens-before關係指定的順序來執行。若是重排序以後的結果,與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM容許這種重排序)。具體規則以下:安全

  • 程序順序規則:一個線程中的每一個操做,happens-before於該線程中的任意後續操做。
  • 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
  • volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
  • 線程啓動規則:若是線程A執行操做ThreadB.start()(啓動線程B),那麼A線程的ThreadB.start()操做happens-before於線程B中的任意操做。
  • 線程終結規則:若是線程A執行操做ThreadB.join()併成功返回,那麼線程B中的任意操做happens-before於線程A從ThreadB.join()操做成功返回。
  • 傳遞性規則:若是A happens-before B,且B happens-before C,那麼A happens-before C。

注:說明一下,網上搜出來有的是8條規則,我不知道還有兩條哪兒來的,JSR-133 裏面只有這六條。網上的還有下面兩條:bash

  • 線程中斷操做:對線程interrupt()方法的調用,happens-before於被中斷線程的代碼檢測到中斷事件的發生,能夠經過Thread.interrupted()方法檢測到線程是否有中斷髮生。
  • 對象終結規則:一個對象的初始化完成,happens-before於這個對象的finalize()方法的開始。

3. 再具體

程序順序規則:一段代碼在單線程中執行的結果是有序的。注意是執行結果,由於虛擬機、處理器會對指令進行重排序。雖然重排序了,可是並不會影響程序的執行結果,因此程序最終執行的結果與順序執行的結果是一致的。故而這個規則只對單線程有效,在多線程環境下沒法保證正確性。多線程

監視器鎖規則:這個規則比較好理解,不管是在單線程環境仍是多線程環境,一個鎖處於被鎖定狀態,那麼必須先執行unlock操做後面才能進行lock操做。併發

volatile變量規則:這是一條比較重要的規則,它標誌着volatile保證了線程可見性。通俗點講就是若是一個線程先去寫一個volatile變量,而後一個線程去讀這個變量,那麼這個寫操做必定是happens-before讀操做的。app

線程啓動規則:假定線程A在執行過程當中,經過執行ThreadB.start()來啓動線程B,那麼線程A對共享變量的修改在接下來線程B開始執行後確保對線程B可見。post

線程終結規則:假定線程A在執行的過程當中,經過制定ThreadB.join()等待線程B終止,那麼線程B在終止以前對共享變量的修改在線程A等待返回後可見。學習

傳遞性規則:提現了happens-before原則具備傳遞性。

特別強調happens-hefore不能理解爲「時間上的前後順序」。 咱們來看以下代碼:

public class VolatileTest {
    private int a = 0;
    private int getA() {
        return a;
    }
    private void setA(int a) {
        this.a = a;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            VolatileTest volatileTest = new VolatileTest();
            Thread thread1 = new Thread(() -> {
                volatileTest.setA(10);
            });
            thread1.start();

            Thread thread2 = new Thread(() -> {
                System.out.print(volatileTest.getA()+" ");
            });
            thread2.start();
        }
    }
}
複製代碼

上面代碼就是一組簡單的setter/getter方法,如今假設如今有兩個線程 thread1 和 thread2,線程 thread1 先(這裏指時間上的先執行)執行setA(10),而後線程 thread2 訪問同一個對象的getA()方法,那麼此時線程B收到的返回值是對少呢?

答案:不肯定

0 0 0 0 10 0 10 10 10 0 10 0 10 10 10 10 10 0 10 10 0 0 0 10 0 10 10 10 0 10 0 10 10 10 0 10 10 0 10 10 10 0 0 10 10 0 10 0 10 10 10 10 10 10 10 10 10 10 0 0 0 10 10 0 10 0 10 0 0 0 10 10 0 10 10 10 10 10 10 10 10 10 10 10 0 10 10 10 0 10 10 10 10 10 0 10 0 10 0 0 
複製代碼

雖然線程 thread1 在時間上先於線程 thread2 執行,可是因爲代碼徹底不適用happens-before規則,所以咱們沒法肯定先 thread2 收到的值時多少。也就是說上面代碼是線程不安全的。

4. Happens-Before與JMM的關係

JMM的設計圖

從圖能夠看出:

  • JMM向程序員提供的happens-before規則能知足程序員的需求。JMM的happens-before規則不但簡單易懂,並且也向程序員提供了足夠強的內存可見性保證(有些內存可見性保證其實並不必定真實存在,好比上面的A happens-before B)。
  • JMM對編譯器和處理器的束縛已經儘量少。從上面的分析能夠看出,JMM實際上是在遵循一個基本原則:只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。例如,若是編譯器通過細緻的分析後,認定一個鎖只會被單個線程訪問,那麼這個鎖能夠被消除。再如,若是編譯器通過細緻的分析後,認定一個volatile變量只會被單個線程訪問,那麼編譯器能夠把這個volatile變量看成一個普通變量來對待。這些優化既不會改變程序的執行結果,又能提升程序的執行效率。

happens-before與JMM的關係

一個happens-before規則對應於一個或多個編譯器和處理器重排序規則。對於Java程序員來講,happens-before規則簡單易懂,它避免Java程序員爲了理解JMM提供的內存可見性保證而去學習複雜的重排序規則以及這些規則的具體實現方法.

5. 小結&參考資料

小結

時間前後順序與happens-before原則之間基本沒有太大的關係,因此咱們在衡量併發安全問題的時候不要受到時間順序的干擾,一切必須以happens-before原則爲準。

簡單的說,happens-before 規則就是爲了讓程序猿更好的理解 JMM 提供的內存可見性而編寫的規則,讓程序猿能避免去學習編譯器和底層編譯原理的重排序規則。

參考資料

相關文章
相關標籤/搜索