執行緒可能進入阻塞,物件可能有「同步」方法——除非同步鎖被釋放,否則執行緒無法存取該物件。一個執行緒等待另一物件,另一物件等待下一物件……最可怕形成**封閉等待鏈**——最後物件等待第一個!所有執行緒陷入無休止相互等待,動彈不得。此即死結。雖不常見,一旦碰到除錯極其艱難。語言本身未直接提供預防幫助,需謹慎設計避免。
為減少死結可能,Java 1.2 「反對」使用這四個方法。
不安全。它會解除執行緒獲得的所有鎖定,若物件處於不連貫狀態(被破壞),其他執行緒能在該狀態下檢查修改。造成微妙局面,難檢查真正問題。**應避免 stop()**,採用 Blocking.java 方式:用標誌告知執行緒何時透過退出 run() 中止。
若執行緒阻塞(如等待輸入),無法像 Blocking.java 輪詢標誌。但仍不該用 stop(),而應用 Thread.interrupt() 中止並退出阻塞代碼。
//: Interrupt.java
class Blocked extends Thread {
public synchronized void run() {
try { wait(); } catch(InterruptedException e) { System.out.println("InterruptedException"); }
System.out.println("Exiting run()");
}
}
public class Interrupt extends Applet {
private Button interrupt = new Button("Interrupt");
private Blocked blocked = new Blocked();
public void init() {
add(interrupt);
interrupt.addActionListener(e -> {
System.out.println("Button pressed");
if(blocked == null) return;
Thread remove = blocked;
blocked = null; // 釋放給 GC
remove.interrupt(); // 關鍵:中斷阻塞
});
blocked.start();
}
} ///:~
wait() 產生阻塞。按下按鈕將參考設 null 釋放 GC,呼叫 interrupt()。首次按下見執行緒正常退出;之後無執行緒可「殺」只見按鈕被按下。
呼叫 suspend() 目標執行緒停下但**仍持有之前獲得的鎖定**。此時其他執行緒無法存取鎖定資源,除非被掛起執行緒恢復。若任何執行緒想恢復目標執行緒同時又試圖使用鎖定資源 → **死結**。
**正確替代方案:** 在 Thread 類中放入標誌指示活動/掛起。標誌指示掛起 → 用 wait() 進入等待。指示恢復 → 用 notify() 重啟。
//: Suspend.java
public class Suspend extends Applet {
private TextField t = new TextField(10);
private Button suspend = new Button("Suspend"), resume = new Button("Resume");
class Suspendable extends Thread {
private int count = 0;
private boolean suspended = false;
public Suspendable() { start(); }
public void fauxSuspend() { suspended = true; }
public synchronized void fauxResume() { suspended = false; notify(); }
public void run() {
while (true) {
try {
sleep(100);
synchronized(this) { while(suspended) wait(); }
} catch (InterruptedException e) {}
t.setText(Integer.toString(count++));
}
}
}
private Suspendable ss = new Suspendable();
public void init() {
add(t);
suspend.addActionListener(e -> ss.fauxSuspend());
add(suspend);
resume.addActionListener(e -> ss.fauxResume());
add(resume);
}
} ///:~
suspended 標誌開關掛起狀態run() 中 synchronized(this) { while(suspended) wait(); } —— wait() 必須同步以使用物件鎖fauxResume() 設 false 並 notify() —— 同步方法確保呼叫 notify() 前取得物件鎖(喚醒的 wait() 使用)wait()/notify() 陷阱類似無法恢復的 suspend(),有相同死結問題。未明確「反對」,或許未來版本(1.2 後)實作,用於可承受死結風險的特殊場合。
Sun 讓技術人員決定語言改動而非行銷人員。技術人員更理解語言本質。犯錯後能理智正視。Java 能持續進步,即使讓程式設計師多少不便。寧願面對不便,不願語言停滯不前。