在Java的多執行緒中,死鎖Deadlock是指兩條或以上的執行緒因互相等待物件的鎖而導致程式無法繼續執行的現象。
下面範例為deadlock的情況(建立執行緒的Runnable
物件是使用Java 8的Lambda語法)。
public class Main {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
Thread a = new Thread(() -> { // Runnable.run()
String threadName = Thread.currentThread().getName();
synchronized (obj1) {
System.out.println(threadName + ": 取得 obj1 的鎖");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(threadName + ": 等待 obj2 的鎖");
synchronized (obj2) {
System.out.println(threadName + ": 取得 obj2 的鎖");
}
}
}, "thread-a");
Thread b = new Thread(() -> { // Runnable.run()
String threadName = Thread.currentThread().getName();
synchronized (obj2) {
System.out.println(threadName + ": 取得 obj2 的鎖");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(threadName + ": 等待 obj1 的鎖");
synchronized (obj1) {
System.out.println(threadName + ": 取得 obj1 的鎖");
}
}
}, "thread-b");
a.start();
b.start();
}
}
執行結果如下。
thread-a: 取得 obj1 的鎖
thread-b: 取得 obj2 的鎖
thread-a: 等待 obj2 的鎖
thread-b: 等待 obj1 的鎖
上面範例建立了兩個物件obj1
及obj2
作為執行緒同步化的對象,及建立兩條執行緒thread-a及thread-b。兩條執行緒中皆有巢狀同步化區塊(nested synchronized blocks),鎖定的物件分別為obj1
及obj2
。
thread-a先在外層的同步化區塊鎖定obj1
,然後在內層的同步化區塊再鎖定obj2
;
thread-b則相反,先鎖定obj2
然後再鎖定obj1
。
當thread-a及thread-b兩條執行緒開始執行後,皆可順利取得外層同步物件的鎖。
而當thread-a要取得obj2
的鎖時,此時obj2
的鎖仍在thread-b,因此thread-a便進入等待狀態;
同樣地,thread-b要取得obj1
的鎖時,因為obj1
的鎖仍在thread-a,因此thread-b也進入等待狀態。
由於thread-a要取得obj2
的鎖,必須等到thread-b執行完;thread-b也是在等待thread-a的obj1
的鎖,
最終形成兩條執行緒互相等待的局面,程式就卡住不執行了,這就是所謂的deadlock。
避免deadlock的方法:
- 避免使用巢狀同步化區塊。巢狀同步化區塊很容易導致執行緒互相等待而造成deadlock
- 只鎖定需要鎖定的資源。例如需要鎖定物件中的某個成員變數,則只要對該變數進行鎖定就好,不要將整個物件鎖定。
- 避免無限期的等待。如果
join()
或wait()
沒指定等待時間,則執行緒會持續等待直到物件的鎖被釋放或被其他執行緒通知才會繼續執行,因此最好指定等待的時間來避免無限期的等待。
deallock在日常生活中常碰到,例如:
- 應徵工作,但公司說你要有工作經驗。
- 情侶吵架後,想如果對方先道歉就原諒對方,結果持續冷戰。
- 健身房一人在踩飛輪,一人在舉啞鈴。踩飛輪的人想等舉啞鈴的人用完就去舉啞鈴,舉啞鈴的人想等踩飛輪的人用完就去踩飛輪。
如果覺得文章有幫助的話還幫忙點個Google廣告,感恩。
秒懂
回覆刪除