本博客系列是學習並發編程過程中的記錄總結。由於文章比較多,寫的時間也比較散,所以我整理了個目錄貼(傳送門),方便查閱。
本文是《深入Java虛擬機》的部分讀書筆記
如果Java內存模型中所有的有序性都僅靠volatile和synchronized來完成,那么有很多操作都將會變得非常啰嗦。
但是我們在編寫Java並發代碼的時候並沒有察覺到這一點,這是因為Java語言中有一個“先行發生”(Happens-Before)的原則。這個原則非常重要,它是判斷數據是否存在競爭,線程是否安全的非常有用的手段。依賴這個原則,我們可以通過幾條簡單規則一攬子解決並發環境下兩個操作之間是否可能存在沖突的所有問題,而不需要陷入Java內存模型苦澀難懂的定義之中。
先行發生是Java內存模型中定義的兩項操作之間的偏序關系,比如說操作A先行發生於操作B,其實就是說在發生操作B之前,操作A產生的影響能被操作B觀察到,“影響”包括修改了內存中共享變量的值、發送了消息、調用了方法等。
下面是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收到的返回值是什么?
我們依次分析一下先行發生原則中的各項規則。由於兩個方法分別由線程A和B調用,不在一個線程中,所以程序次序規則在這里不適用;由於沒有同步塊,自然就不會發生lock和unlock操作,所以管程鎖定規則不適用;由於value變量沒有被volatile關鍵字修飾,所以volatile變量規則不適用;后面的線程啟動、終止、中斷規則和對象終結規則也和這里完全沒有關系。因為沒有一個適用的先行發生規則,所以最后一條傳遞性也無從談起,因此我們可以判定,盡管線程A在操作時間上先於線程B,但是無法確定線程B中getValue()方法的返回結果,換句話說,這里面的操作不是線程安全的。
我們至少有兩種比較簡單的方案可以選擇:要么把getter/setter方法都定義為synchronized方法,這樣就可以套用管程鎖定規則;要么把value定義為volatile變量,由於setter方法對value的修改不依賴value的原值,滿足volatile關鍵字使用場景,這樣就可以套用volatile變量規則來實現先行發生關系。
通過上面的例子,我們可以得出結論:一個操作“時間上的先發生”不代表這個操作會是“先行發生”。那如果一個操作“先行發生”,是否就能推導出這個操作必定是“時間上的先發生”呢?很遺憾,這個推論也是不成立的。
上面兩個例子綜合起來證明了一個結論:時間先后順序與先行發生原則之間基本沒有因果關系,所以我們衡量並發安全問題的時候不要受時間順序的干擾,一切必須以先行發生原則為准。