所謂的死結,是指兩個執行緒分別取得了鎖定,互相等待另外一個執行緒解除鎖定的現象,發生死結的時候,哪個執行緒都無法繼續執行下去,這時候程式就會不斷等待。
假設有兩個人(Alice and Boddy)在用餐,餐具只有兩個,分別是spoon以及fork,Alice的習慣是左手拿spoon,接著右手拿fork才會開始用餐;Boddy的習慣是左手拿spoon,接著右手拿fork之後才開始用餐。 現在寫一個程式去模擬整個過程,一個不小心可能會出現一個情況:Alice左手拿了spoon,等待fork,但Boddy左手已經拿了fork,等待spoon,這樣互相等待共享資源的情況,就會產生deadlock。
以下是會產生deadlock的程式。
建立shared resource,如下的Tool.java
public class Tool {
private final String name;
public Tool(String name) {
this.name = name;
}
public String toString() {
return "[ " + name + " ]";
}
}
Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Testing EaterThread, hit CTRL+C to exit.");
Tool spoon = new Tool("Spoon");
Tool fork = new Tool("Fork");
new EaterThread("Alice", spoon, fork).start();
new EaterThread("Bobby", fork, spoon).start();
}
}
EaterThread.java
public class EaterThread extends Thread {
private String name;
private final Tool lefthand;
private final Tool righthand;
public EaterThread(String name, Tool lefthand, Tool righthand) {
this.name = name;
this.lefthand = lefthand;
this.righthand = righthand;
}
public void run() {
while (true) {
eat();
}
}
public void eat() {
//Alice拿起了spoon,在還沒拿到fork之前,bob已經拿到了fork
synchronized (lefthand) {
System.out.println(name + " takes up " + lefthand + " (left).");
//兩個人都在等對方的資源
synchronized (righthand) {
System.out.println(name + " takes up " + righthand + " (right).");
System.out.println(name + " is eating now, yam yam!");
System.out.println(name + " puts down " + righthand + " (right).");
}
System.out.println(name + " puts down " + lefthand + " (left).");
}
}
}
要怎麼解決deadlock的問題?當然就是改寫程式囉!
要先了解deadlock會發生在下列三種情況都成立的時候:
- 具有多個shared resource,例如上述的spoon以及fork。
- 執行緒鎖定一個shared resource時,還沒解除鎖定就想去鎖定另外一個shared resource。
- 取得shared resource的順序不固定,例如先拿spoon再拿fork以及先拿fork再拿spoon。
只要打破其中一種條件,就能解除deadlock。
現在來打破第三個條件,讓拿resource的順序可以一致。
修改Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Testing EaterThread, hit CTRL+C to exit.");
Tool spoon = new Tool("Spoon");
Tool fork = new Tool("Fork");
//修改順序
new EaterThread("Alice", spoon, fork).start();
new EaterThread("Bobby", spoon, fork).start();
}
}
這樣就可以打破死結。
第二個條件比較難打破,因為必須同時拿兩個工具才能開始吃東西。
現在來試著打破第一個條件,我們可以試著把spoon以及fork合併在一起。
定義一個Pair.java
public class Pair {
private final Tool lefthand;
private final Tool righthand;
public Pair(Tool lefthand, Tool righthand) {
this.lefthand = lefthand;
this.righthand = righthand;
}
public String toString() {
return "[ " + lefthand + " and " + righthand + " ]";
}
}
修改Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Testing EaterThread, hit CTRL+C to exit.");
Tool spoon = new Tool("Spoon");
Tool fork = new Tool("Fork");
Pair pair = new Pair(spoon, fork);
new EaterThread("Alice", pair).start();
new EaterThread("Bobby", pair).start();
}
}
這時候EaterThread.java也就可以被修改成只存取一個resource。
public class EaterThread extends Thread {
private String name;
private final Pair pair;
public EaterThread(String name, Pair pair) {
this.name = name;
this.pair = pair;
}
public void run() {
while (true) {
eat();
}
}
public void eat() {
//只鎖定pair這個shared resource
synchronized (pair) {
System.out.println(name + " takes up " + pair + ".");
System.out.println(name + " is eating now, yam yam!");
System.out.println(name + " puts down " + pair + ".");
}
}
}
deadlock是個很有趣且在Multithread上經常會碰到的問題,但只要打破三個條件中的其中一個就可以讓deadlock不成立。