幾乎所有基於 AWT/Swing 的 GUI 應用都需要多線程,但多線程引入的死鎖、競態條件、線程饑餓等問題令初學者望而生畏。本文系統介紹 Java Thread API、同步機制、細粒度鎖、信號量模式、volatile 語義、兩大經典同步陷阱(死鎖、同步失效)、阻塞調用導致的「不可訪問線程」問題、協作式/搶佔式模型下的編程規範,以及 AWT/Swing 事件分發線程(EDT)協作準則,構建多線程開發全景知識圖譜。
Runnable(推薦)或繼承 Thread,調用 start() 異步啟動,run() 為執行體。synchronized 方法/塊隱式獲取/釋放鎖,可重入,異常自動釋放。為提升併發度,將不同共享資源保護域分離:
class FineGrainLock {
final Object xLock = new Object(), yLock = new Object();
Data x, y;
void accessX() { synchronized(xLock) { /* 訪問 x */ } }
void accessY() { synchronized(yLock) { /* 訪問 y */ } }
void accessBoth() {
synchronized(xLock) { synchronized(yLock) { /* 訪問 x,y */ } }
// 固定順序 xLock → yLock 防死鎖
}
}
控制有限資源(如數據庫連接池)訪問:
class Semaphore {
private int permits;
public Semaphore(int n) { permits = n; }
public synchronized void acquire() throws InterruptedException {
while (permits == 0) wait();
permits--;
}
public synchronized void release() { permits++; notify(); }
}
現代替代:java.util.concurrent.Semaphore(AQS 實現、公平性、批量操作、可中斷)。
成因:線程 A 持有鎖 1 等待鎖 2,線程 B 持有鎖 2 等待鎖 1,形成環路等待。
預防鐵律:
Lock.tryLock(timeout) 超時釋放已持有鎖重試。現象:代碼有 synchronized 但無互斥效果。
根因:不同線程同步於不同鎖對象。典型:synchronized(this) 但 this 為不同 Task 實例。
修正:所有協作線程共享同一把鎖(共享 Runnable 實例、static final 鎖、構造器傳入共享鎖)。
volatile int i; i++ 非原子)。volatile boolean shutdown)、單次發布(volatile Helper helper)、獨立狀態變量。場景:同步方法中調用阻塞 IO(socket.read()、ObjectInputStream.readObject()),線程阻塞且**持有鎖不釋放**,導致其他線程無法訪問該對象任何同步方法,甚至無法發送「取消 IO」命令。
解決方案:
cancel()/close() 方法,內部調用 socket.close() 釋放阻塞線程。JVM 規範未強制調度模型,代碼必須同時正確運行於:
yield()、sleep()、wait())。對策:yield()/sleep()(持有鎖讓出 CPU,其他線程獲取不到鎖)。yield()。wait() 釋放鎖並等待(需配合 notifyAll() 喚醒)。原子操作:32 位及以下變量讀寫(除 long/double)為原子;64 位 long/double 非原子(需 volatile 或同步)。
actionPerformed 等回調在 EDT 運行,需快速返回;耗時邏輯提交給後台線程(ExecutorService、SwingWorker)。SwingUtilities.invokeLater(Runnable)(異步)或 invokeAndWait(Runnable)(同步,慎用死鎖風險)提交給 EDT。final,粒度適中,獲取順序固定防死鎖。Condition / BlockingQueue / CountDownLatch 等高級同步器,避免手寫 wait/notify。volatile 僅用於單一變量可見性/有序性,非複合操作。invokeLater 回調更新 UI。jstack 分析、Java Mission Control 監控,驗證併發性能與穩定性。多線程難在正確性而非語法。遵循原則、善用工具、嚴格測試,方能馴服併發之獸。