先行發生原則(Happens-Before)

本博客系列是學習併發編程過程當中的記錄總結。因爲文章比較多,寫的時間也比較散,因此我整理了個目錄貼(傳送門),方便查閱。html

併發編程系列博客傳送門java


本文是《深刻Java虛擬機》的部分讀書筆記編程

若是Java內存模型中全部的有序性都僅靠volatile和synchronized來完成,那麼有不少操做都將會變得很是囉嗦。安全

可是咱們在編寫Java併發代碼的時候並無察覺到這一點,這是由於Java語言中有一個「先行發生」(Happens-Before)的原則。這個原則很是重要,它是判斷數據是否存在競爭,線程是否安全的很是有用的手段。依賴這個原則,咱們能夠經過幾條簡單規則一攬子解決併發環境下兩個操做之間是否可能存在衝突的全部問題,而不須要陷入Java內存模型苦澀難懂的定義之中。併發

先行發生是Java內存模型中定義的兩項操做之間的偏序關係,好比說操做A先行發生於操做B,其實就是說在發生操做B以前,操做A產生的影響能被操做B觀察到,「影響」包括修改了內存中共享變量的值、發送了消息、調用了方法等app

下面是Java內存模型下一些「自然的」先行發生關係,這些先行發生關係無須任何同步器協助就已經存在,能夠在編碼中直接使用。若是兩個操做之間的關係不在此列,而且沒法從下列規則推導出來,則它們就沒有順序性保障,虛擬機能夠對它們隨意地進行重排序。函數

  • 程序次序規則(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;

pubilc void setValue(int value){
    this.value = value;
}

public int getValue(){
    return value;
}

上面的代碼顯示的是一組再普通不過的getter/setter方法,假設存在線程A和B,線程A先(時間上的前後)調用了setValue(1),而後線程B調用了同一個對象的getValue(),那麼線程B收到的返回值是什麼?this

咱們依次分析一下先行發生原則中的各項規則。因爲兩個方法分別由線程A和B調用,不在一個線程中,因此程序次序規則在這裏不適用;因爲沒有同步塊,天然就不會發生lock和unlock操做,因此管程鎖定規則不適用;因爲value變量沒有被volatile關鍵字修飾,因此volatile變量規則不適用;後面的線程啓動、終止、中斷規則和對象終結規則也和這裏徹底沒有關係。由於沒有一個適用的先行發生規則,因此最後一條傳遞性也無從談起,所以咱們能夠斷定,儘管線程A在操做時間上先於線程B,可是沒法肯定線程B中getValue()方法的返回結果,換句話說,這裏面的操做不是線程安全的。編碼

咱們至少有兩種比較簡單的方案能夠選擇:要麼把getter/setter方法都定義爲synchronized方法,這樣就能夠套用管程鎖定規則;要麼把value定義爲volatile變量,因爲setter方法對value的修改不依賴value的原值,知足volatile關鍵字使用場景,這樣就能夠套用volatile變量規則來實現先行發生關係。

經過上面的例子,咱們能夠得出結論:一個操做「時間上的先發生」不表明這個操做會是「先行發生」。那若是一個操做「先行發生」,是否就能推導出這個操做一定是「時間上的先發生」呢?很遺憾,這個推論也是不成立的。

上面兩個例子綜合起來證實了一個結論:時間前後順序與先行發生原則之間基本沒有因果關係,因此咱們衡量併發安全問題的時候不要受時間順序的干擾,一切必須以先行發生原則爲準

相關文章
相關標籤/搜索