兩種線程調度模型
| 模型 | 特徵 | Java 實現 | 開發者責任 |
| 搶佔式 | OS 強制切換,時間片輪轉 | 現代 JVM (HotSpot) 在多核預設 | 正確同步共享資源(鎖、volatile、原子類) |
| 協作式 | 線程主動讓出 CPU | 早期 JVM、單核綠色線程 | 適時呼叫 yield()/sleep(),避免佔用 |
關鍵結論:Java 規範不保證調度模型,代碼必須在兩種模型下都正確工作。
原子操作與 64 位元型別陷阱
- 原子:
≤ 32 位 變數讀寫、volatile 讀寫、引用賦值
- 非原子:
long、double(64 位)非 volatile 讀寫(可能分兩次 32 位操作)
// 危險:非原子寫入可能被讀到半舊半新值
long counter = 0;
// 安全:volatile 或 AtomicLong
volatile long counter = 0;
AtomicLong counter = new AtomicLong(0);
協作式模型下的正確讓出
yield() 陷阱
// 錯誤:持有鎖時 yield,其他線程無法獲得鎖
synchronized (lock) {
// 臨界區
Thread.yield(); // 鎖未釋放!其他線程阻塞在 synchronized 外
}
// 正確:同步塊外 yield
synchronized (lock) { /* 臨界區 */ }
Thread.yield(); // 已釋放鎖
sleep() 與 wait() 差異
| 方法 | 釋放鎖 | 喚醒方式 | 適用場景 |
sleep(ms) | 否 | 時間到 / 中斷 | 定時輪詢、限流延遲 |
wait() | 是 | notify()/中斷/超時 | 條件等待、生產消費 |
細粒度鎖下的 wait() 限制
// 僅釋放當前物件鎖,其他細粒度鎖仍持有
synchronized (lock1) {
synchronized (lock2) {
lock2.wait(); // 僅釋放 lock2,lock1 仍持有!
}
}
// 解決:避免巢狀同步中 wait,或使用 Lock+Condition
AWT/Swing 事件分派線程 (EDT)
核心規則
- 所有 Swing 元件建構、修改、查詢必須在 EDT
- 耗時運算絕不能在 EDT,否則 UI 凍結
- 事件監聽器 (
ActionListener 等) 在 EDT 執行,需快速返回
錯誤模式:EDT 執行耗時任務
button.addActionListener(e -> {
// 錯誤:EDT 執行下載,UI 凍結
File file = downloadLargeFile(url);
textArea.setText("完成");
});
正確模式:SwingWorker / invokeLater
button.addActionListener(e -> {
// 正確:背景執行緒下載
new SwingWorker() {
@Override
protected File doInBackground() {
return downloadLargeFile(url); // 背景執行
}
@Override
protected void done() {
try {
textArea.setText("完成: " + get()); // EDT 更新 UI
} catch (Exception ex) { handleError(ex); }
}
}.execute();
});
非 EDT 線程安全更新 UI
// 背景執行緒計算完成,需更新 UI
// 錯誤:直接操作 Swing 元件
// label.setText("結果"); // 競態條件、繪製異常
// 正確 1:invokeLater(非阻塞)
SwingUtilities.invokeLater(() -> label.setText("結果"));
// 正確 2:invokeAndWait(阻塞等待)
try {
SwingUtilities.invokeAndWait(() -> label.setText("結果"));
} catch (Exception e) { handleError(e); }
原理:請求放入 EDT 事件佇列,EDT 循環處理時執行 Runnable.run(),保證線程安全。
IO 與 GUI 必須多線程的原因
- 阻塞 IO:
InputStream.read() 可能無限等待,單線程會凍結整個程式
- GUI 事件循環:需持續處理重繪、滑鼠、鍵盤事件,耗時任務會阻塞循環
總結:多線程開發鐵律
- 共享資源必須同步(無論調度模型)
- 長任務移出 EDT(SwingWorker/執行緒池)
- UI 更新回 EDT(invokeLater/invokeAndWait)
- 避免持有鎖時 sleep/yield/wait
- 設計前先分析共享資料、鎖粒度、線程邊界
理解 Java 線程模型的不確定性、EDT 單線程規則、同步原語正確用法,是編寫健壯併發應用的基石。
- 上一篇文章: Java 7 異常處理增強:Multi-catch 與 Final Rethrow 實戰指南
- 下一篇文章: Java 匯入機制深度解析:單類型 vs 按需匯入、編譯期解析與最佳實踐
Copyright ©
程式碼之家 All Rights Reserved