安全並發之先行發生原則
先行發生原則,可以幫你判定是否並發安全的,從而不必去猜測是否是線程安全了!
如果Java內存模型中所有有序性都靠volatile和synchronized來完成,那麽編寫代碼會很繁瑣,但日常Java開發中並沒有感受到這一點,正是因為Java語言的“先行發生”原則。這個原則非常重要,它是判斷數據是否存在競爭、線程是否安全的主要依據。
先行發生是Java內存模型中定義的兩項操作數之間的偏序關系,如果說操作A先行發生於操作B,就是說在發生操作B之前,操作A產生的影響能被操作B觀察到,“影響”包括修改了內存中共享變量的值、發送了消息、調用了方法等。
下面是Java內存模型中一些“天然的”先行發生關系,這些先行發生關系無需任何同步協助器協作即可存在,可以直接在編碼中使用。如果兩個關系不在此列,而又無法通過這些關系推導出來,它們的順序就無法保證,虛擬機可以對它們任意重排序。
程序次序法則: 線程中的每一個動作A都happens-before於該線程中的每一個動作B,其中,在線程中,所有的動作B都出現在動作A之後
管程鎖定規則: 對於一個監視器鎖的unLock 操作happens-before於每個後續對同一監視器鎖的Lock操作
volatile變量法則: 對volatile域的寫入操作happens-before於每個後續對同一個yu域的讀操作。
線程啟動法則: 在同一個線程裏,對Thread.start的調用會happens-before於每一個啟動線程中的動作。
線程終結法則: 線程中的所有動作都happens-before於其它線程檢測到這個線程已經終結,或者從Thread.jonin調用成功返回,或者Thread.isAlive返回false.
中斷法則: 一個線程調用另一個線程的interrupt happens-before 於被中斷的線程發現中斷(通過拋出InterruptedException 或者調用isInterrupted和interrupted)
終結法則: 一個對象的構造函數的結束happens-before於這個對象finalizer的開始
傳遞性: 如果 A happens-before 於 B,且 B happens-before 於 C,則 A happens-before 於C。
java無須任何手段即可保證上面的先行發生規則成立,下面那個例子看一下:
private int value = 0;public void setValue(int value){ this.value = value; } public int getValue(){ return value; }
假設A、B兩個線程,線程A先(時間上的先後)執行setValue(1), 然後線程B調用同一對象的getValue(),那麽線程B收到的返回值是什麽?
依次分析一下先行發生原則中的各個原則:
由於兩個方法分別在不同的線程中被調用,程序次序原則不適用;
沒有同步塊,自然不會發生lock和unlock操作,管程鎖定原則不適用;
value變量沒有被volatile修飾,volatile變量原則不適用;
後面的線程啟動、中斷、終止原則也毫無關系;
沒有一個適用的原則,傳遞性也不適用。
所以說線程B得到的結果不確定是0還是1,換句話說,這裏面的操作不是線程安全的。
怎麽修復呢?getter/setter 定義synchronized方法;或者把value變量定義volatile變量,就回到了先行發生原則上了。
private volatile int value = 0;
另外,先行發生並不代表一定是先發生!
時間先後順序於先行發生的原則之間基本沒有太大的關系。
比如如下代碼中,i, j 的取值問題:
//同一個線程中執行 int i = 1; int j = 2; // doSth... volt = 10; // 假設volt為 volatile 修飾的
根據程序次序規則,”int i = 1”的操作先行發生於”int j = 2”,但是”int j = 2”的代碼完全可能先被處理器執行,這並不影響先行發生原則的正確性,因為我們在這條線程中並沒有辦法感知到這點。
先行發生原則,可以幫你判定是否並發安全的,從而不必去猜測是否是線程安全了!
---- 摘自《深入理解Java虛擬機》
安全並發之先行發生原則