并發–先行發生原則及案例分析
标簽(空格分隔): Java-JVM
先行發生原則
先行發生原則是判斷資料是否存在競争、線程是否安全的主要依據。
- 程式次序規則(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 Interrupt Rule):對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷發生。
- 對象終結規則(Finalizer Rule):一個對象的初始化完成(構造函數執行結束)先行發生于它的finalize()方法的開始。
- 傳遞性(Transitivity):如果操作A先行發生于操作B,操作B先行發生于操作C,那就可以得出操作A先行發生于操作C的結論。
摘自:《深入了解Java虛拟機——JVM進階特性與最佳實踐(第2版)》
案例分析(1)–線程安全類Vector
線程安全類型Vector,隻是相對線程安全,并非絕對線程安全
private static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) {
while (true) {
for (int i = ; i < ; i++) {
vector.add(i);
}
Thread removeThread = new Thread(new Runnable() {
public void run() {
for (int i = ; i < vector.size(); i++) {
vector.remove(i);
}
}
});
Thread getThread = new Thread(new Runnable() {
public void run() {
for (int i = ; i < vector.size(); i++) {
// 嘗試加入首先判斷i是否在vector size範圍内,結果同樣報錯,
// if (i < vector.size()) {
// continue;
// }
vector.get(i);
}
}
});
removeThread.start();
getThread.start();
//不要同時産生過多的線程,否則會導緻作業系統假死
while (Thread.activeCount() > ) ;
}
}
- 程式次序:不滿足,
與remove(i)
在控制流順序沒有先行發生關系;get(i)
- 管程鎖定:不滿足,
與remove(i)
方法都是synchronized修飾,但各自持有不同的鎖,不滿足管程鎖定要求的同一個鎖;get(i)
- volatile變量:不滿足,沒有volatile修飾變量,無視;
- 線程啟動:不滿足,
先與removeThread.start()
,vector.remove(i)
先于getThread.start()
,但後兩者明顯沒有關系;vector.get(i)
- 線程終止:不滿足;
- 線程中斷:不滿足;
- 對象終結:不滿足,不存在對象終結的關系;
- 傳遞性:不滿足,加入
驗證作為參考,假定A是size()
,B是remove()
驗證,C是size()
,B先于C,但A可能介乎于BC之間,也可能在B之前。是以不符合傳遞性。get()
結論:Vector作為相對線程安全對象,其單個方法帶Synchronized修飾,是相對線程安全的,但Vector方法之間不是線程安全的,不能保證多個方法作用下的資料一緻性。執行例子
get()
會報錯:
java.lang.ArrayIndexOutOfBoundsException
。