<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 深入理解 happens-before 原則

    在前面的文章中,我們深入了解了 Java 內存模型,知道了 Java 內存模型誕生的意義,以及其要解決的問題。最終我們知道:Java 內存模型就是定義了 8 個基本操作以及 8 個規則,只要遵守這些規則的并發操作,那么它們就是安全的。

    即使強如樹哥的人,看了這 16 條規則也很頭疼。它們太過于繁瑣了,非常不利于我們日常代碼的編寫。為了能幫助編程人員理解,于是就有了與其相等價的判斷原則 —— 先行發生原則,它可以用于判斷一個訪問在并發環境下是否安全。

    到這里,我們需要明白:happens-before 原則是對 Java 內存模型的簡化,讓我們更好地寫出并發代碼。就像 Java 語言等高級語言,之于匯編語言、機器語言等低級語言一樣,可以讓編程人員免受「皮肉之苦」。

    什么是 happens-before?

    happens-before 指的是 Java 內存模型中兩項操作的順序關系。例如說操作 A 先于操作 B,也就是說操作 A 發生在操作 B 之前,操作 A 產生的影響能夠被操作 B 觀察到。這里的「影響」包括:內存中共享變量的值、發送了消息、調用了方法等。

    舉個很簡單的例子:下面代碼里 i=1 在線程 A 中執行,而 j=i 在線程 B 中執行。因為 i=1 操作先于 j=i 執行,那么 i=1 操作的結果就應該能夠被線程 B 觀察到。

    // 在線程 A 中執行
    i = 1;
    // 在線程 B 中執行
    j = i;
    

    Java 內存模型下一共有 8 條 happens-before 規則,如果線程間的操作無法從如下幾個規則推導出來,那么它們的操作就沒有順序性保障,虛擬機或者操作系統就能隨意地進行重排序,從而可能會發生并發安全問題。

    • 程序次序規則(Program Order Rule):在一個線程內,按照程序代碼順序,書寫在前面的操作先行發生于書寫在后面的操作。準確地說,應該是控制流順序而不是程序代碼順序,因為要考慮分支、循環等結構。
    • 管程鎖定規則(Monitor Lock Rule):一個unlock操作先行發生于后面對同一個鎖的lock操作。這里必須強調的是同一個鎖,而“后面”是指時間上的先后順序。
    • volatile變量規則(Volatile Variable Rule):對一個volatile變量的寫操作先行發生于后面對這個變量的讀操作,這里的“后面”同樣是指時間上的先后順序。
    • 線程啟動規則(Thread Start Rule):Thread對象的start()方法先行發生于此線程的每一個動作。
    • 線程終止規則(Thread Termination Rule):線程中的所有操作都先行發生于對此線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行。
    • 線程中斷規則(Thread Interruption Rule):對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷發生。
    • 對象終結規則(Finalizer Rule):一個對象的初始化完成(構造函數執行結束)先行發生于它的finalize()方法的開始。
    • 傳遞性(Transitivity):如果操作A先行發生于操作B,操作B先行發生于操作C,那就可以得出操作A先行發生于操作C的結論。

    極簡實踐案例

    Java 語言無須任何同步手段保障,就能成立的先行發生規則,就只有上面這些了。下面舉個例子來說明如何用這些規則去判斷操作是否具備順序性,是否是線程安全的。

    private int value = 0;
    
    public void setValue(int value){
        this.value = value;
    }
    
    public int getValue(){
        return value;
    }
    

    上面的代碼是一組很普通的 getter/setter 方法。假設線程 A 和 B,線程 A 先(時間上的先后)調用了 setValue(1),之后線程 B 調用了同一個對象的 getValue(),那么線程 B 收到的返回值是什么?

    我們依次分析一下先行發生原則中的各項規則:

    1. 首先,由于兩個方法分別由線程A和線程B調用,不在一個線程中,所以程序次序規則在這里不適用。
    2. 接著,由于沒有同步塊,自然就不會發生lock和unlock操作,所以管程鎖定規則不適用。
    3. 繼續,由于value變量沒有被volatile關鍵字修飾,所以volatile變量規則不適用。
    4. 繼續,后面的線程啟動、終止、中斷規則和對象終結規則也和這里完全沒有關系。
    5. 最后,因為沒有一個適用的先行發生規則,所以最后一條傳遞性也無從談起。

    因此,即使我們知道線程 A 在操作時間上先于線程 B,但我們還是無法確定線程 B getValue() 方法的返回結果。換句話說,這里面的操作不是線程安全的。

    那怎么修復這個問題呢?

    我們至少有兩種比較簡單的方案可以選擇:

    1. 第一種,要么把 getter/setter 方法都定義為 synchronized 方法,這樣就可以套用管程鎖定規則。
    2. 第二種,要么把 value 定義為 volatile 變量,由于 setter 方法對 value 的修改不依賴 value 的原值,滿足 volatile 關鍵字使用場景,這樣就可以套用 volatile 變量規則來實現先行發生關系。

    通過上面這個案例,我們知道:一個操作時間上線發生,不代表這個操作會「先行發生」。 那如果一個操作「先行發生」,是否就能推導出這個操作必定是時間上先發生呢?其實并不能,因為有可能發生指令重排序。

    // 如下操作在同一個線程中執行
    int j = 1;
    int j = 2;
    

    上述代碼在同一線程中執行,根據程序執行次序規則,int i = 1; 的操作先行發生于 int j = 2;,但 int j =2 的代碼有可能被處理器先執行,因為它們不相互依賴,不影響先行發生原則的正確性。

    上述這兩個案例綜合起來證明了一個結論:時間先后順序與先行發生原則之間基本沒有太大的關系,所以我們衡量并發安全問題的時候不要受到時間順序的干擾,一切必須以先行發生原則為準。

    總結

    happens-before 原則一共有 8 條原則,它是對 Java 內存模型規則的簡化,幫助編程人員提高編程效率。

    時間先后順序與先行發生原則之間基本沒有太大的關系,我們衡量并發安全問題的時候不要受到時間順序的干擾,一切必須以先行發生原則為準。

    深入理解 happens-before 原則

    posted @ 2022-06-27 10:19  陳樹義  閱讀(146)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看