幾乎所有採用 AWT/Swing 的圖形應用都離不開多執行緒,但多執行緒程式充滿隱形陷阱:競態條件、死結、活結、執行緒飢餓、GUI 凍結。本文系統梳理 Java 多執行緒核心機制、常見陷阱與解決方案、兩大執行緒模型適配策略、Swing/AWT 線程協作規範,為您構建寫出正確、高效、可維護並發程式的知識體系。
// 方式 1:實作 Runnable(推薦,保留繼承彈性)
class Task implements Runnable {
public void run() { /* 執行緒邏輯 */ }
}
new Thread(new Task()).start();
// 方式 2:繼承 Thread
class MyThread extends Thread {
public void run() { /* 執行緒邏輯 */ }
}
new MyThread().start();
鐵律:必須呼叫 start() 啟動,直接呼叫 run() 只是普通方法呼叫,不會建立新執行緒!
synchronized 方法/區塊 = 獲取物件鎖public class CopyMachine {
// 方法級同步:鎖住 this
public synchronized void makeCopies(Document d, int n) { ... }
// 區塊級同步:可指定鎖物件
public void loadPaper() {
synchronized(this) { /* 同步程式碼 */ }
}
}
將不同資料綁定不同鎖,提升並行度:
class FineGrainLock {
private final Object xLock = new Object();
private final Object yLock = new Object();
private Data x, y;
public void updateX() {
synchronized(xLock) { /* 只操作 x */ }
}
public void updateY() {
synchronized(yLock) { /* 只操作 y */ }
}
public void updateBoth() {
synchronized(this) { /* 操作 x 和 y,需原子性時 */ }
}
}
注意: 細粒度鎖無法用 synchronized 方法修飾符,必須用同步區塊。
經典「生產者-消費者」模式實作:
class Semaphore {
private int permits;
public Semaphore(int permits) { this.permits = permits; }
public synchronized void acquire() throws InterruptedException {
while (permits == 0) wait(); // 必須在 while 循環中 wait,防止虛假喚醒
permits--;
}
public synchronized void release() {
permits++;
notify(); // 喚醒一個等待執行緒
}
}
優於輪詢: 執行緒阻塞等待而非忙等待,節省 CPU、降低 OS 切換開銷。
四個必要條件(同時滿足): 互斥、佔有且等待、不可搶佔、循環等待
預防策略:
tryLock() 失敗則釋放已持有鎖,延遲重試synchronized 或 AtomicInteger)場景: 同步方法中執行 I/O、Thread.sleep()、資料庫查詢 → 執行緒阻塞但持有鎖 → 其他執行緒無法存取該物件任何同步方法
解法:
public void cancel() { /* 非同步,可中斷阻塞 */ }java.util.concurrent 非阻塞資料結構| 模型 | 特徵 | 程式設計要求 |
|---|---|---|
| 搶占式 | OS 任意時間切換執行緒 | 正確使用鎖保護共享資料;32 位以下寫入為原子,64 位需 volatile/synchronized |
| 協作式 | 執行緒主動讓出 CPU | 定期呼叫 yield()/sleep();不可在同步區塊中 yield(不釋放鎖) |
最佳實踐: 寫出模型無關的正確程式——正確同步 + 避免在鎖內長時間運算/阻塞 + 善用 wait()/notify() 釋放鎖並等待條件。
// 非同步提交:不等待完成,適合大多數場景
SwingUtilities.invokeLater(() -> {
label.setText("Updated from background thread");
tableModel.addRow(newData);
});
// 同步提交:等待完成,需取得回傳值時使用
SwingUtilities.invokeAndWait(() -> {
return textField.getText();
});
原理: 將 Runnable 封裝任務送入 EDT 事件佇列,由 EDT 依序執行,保證執行緒安全。
// ❌ 背景執行緒直接操作 Swing 元件 — 不安全,可能導致資料損毀/死結
new Thread(() -> {
tableModel.addRow(data); // 直接修改模型
}).start();
// ✅ 正確:提交給 EDT
SwingUtilities.invokeLater(() -> tableModel.addRow(data));
自 Java 5 起,強烈建議優先使用高層級並發工具,而非手寫底層同步:
| 類別 | 用途 | 替代目標 |
|---|---|---|
| ExecutorService | 執行緒池管理、任務提交、生命週期控制 | 手動 new Thread() |
| Future/CompletableFuture | 非同步運算結果、組合、異常處理 | wait/notify 協作 |
| ConcurrentHashMap | 高並發 Map,分段鎖/無鎖讀取 | Collections.synchronizedMap / Hashtable |
| CopyOnWriteArrayList | 讀多寫少列表,快照迭代器 | 同步 List |
| AtomicInteger/Long/Reference | 無鎖原子操作(CAS) | synchronized 計數器 |
| CountDownLatch/CyclicBarrier | 執行緒同步屏障 | wait/notify 手寫 |
| Semaphore | 資源池許可證控制 | 自訂訊號量 |
| ReentrantLock/ReadWriteLock | 可中斷、可計時、公平鎖、讀寫分離 | synchronized |
| BlockingQueue | 生產者-消費者緩衝隊列 | wait/notify 手寫 |
java.util.concurrent 經過專家驗證,效能與正確性雙重保障invokeLaterExecutorService.shutdown() + awaitTermination(),拒絕 System.exit() 暴力殺進程多執行緒程式設計本質上是狀態管理的藝術。掌握核心機制、敬畏並發複雜度、善用現代工具、建立防禦性編程習慣,您就能駕馭 Java 並發,寫出經得起生產環境考驗的健壯系統。