Java 歷經棄用 Thread.stop/suspend/resume 後,確立協作式中斷模型。本文從 JVM 底層實作、中斷語義、阻塞操作響應、到任務框架編程準則,建立完整認知體系。
本質:JVM 為每個執行緒維護一個 boolean interrupted 標記,interrupt() 僅設定此標記,執行緒代碼必須主動檢查並響應。
// 使用者手動實作等價邏輯 (不具備 JVM 整合優勢)
volatile boolean isInterrupted;
while (!isInterrupted) {
compute();
}
// 缺點: 1. compute 執行久無法即時響應 2. 無法中斷 wait/sleep 阻塞
// Thread.java 關鍵方法
public static boolean interrupted() {
return currentThread().isInterrupted(true); // native, 獲取並清除
}
public boolean isInterrupted() {
return isInterrupted(false); // native, 僅獲取
}
private native boolean isInterrupted(boolean ClearInterrupted); // JVM 實作
public void interrupt() {
// 1. 設定 interrupted = true
// 2. 若阻塞於 wait/sleep/join → 拋 InterruptedException 並清除標記
// 3. 若阻塞於 NIO Channel/Selector → 相應異常/喚醒
}
關鍵優勢:JVM 在執行緒調度點檢查標記,實現 wait/sleep/join 的「盡快拋出 InterruptedException」。
| 阻塞類型 | JVM 響應 | 處理方式 |
|---|---|---|
| Object.wait / Thread.sleep / Thread.join | 拋 InterruptedException,清除標記 | catch 並重設標記或向上拋 |
| InterruptibleChannel I/O | 關閉 Channel,ClosedByInterruptException | catch 關閉資源 |
| Selector.select | wakeup,設定標記 | 檢查標記退出迴圈 |
| 傳統 Socket I/O (InputStream.read) | 無響應 | 呼叫 Socket.close() 觸發 SocketException |
| 內部鎖 | 無響應 | 改用 Lock.lockInterruptibly() |
socket.close() → 阻塞 read() 拋 SocketExceptionselector.wakeup() → select() 返回InterruptedException準則一:除非知道執行緒中斷策略,否則不應中斷它
thread.interrupt() 破壞封裝,執行緒可能屬於執行緒池、被框架管理Future.cancel(true)、ExecutorService.shutdownNow()準則二:任務代碼不應假設中斷對執行緒的含義
InterruptedException 後絕不可吞噬 (空 catch)Thread.currentThread().interrupt() 重設標記、或向上拋出、或封裝為 RuntimeExceptionpublic class InterruptibleTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 1. 非阻塞工作 + 定期檢查
doPhase1();
if (Thread.interrupted()) break; // 檢查並清除
// 2. 可中斷阻塞操作
try {
Thread.sleep(1000); // 或 lock.lockInterruptibly()
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重設標記
break; // 退出迴圈
}
// 3. I/O 操作 (需配合 NIO/關閉機制)
doIO();
}
} finally {
cleanup(); // 確保資源釋放
}
}
}
// 框架端正確取消
Future> future = executor.submit(new InterruptibleTask());
// ...
future.cancel(true); // 框架負責 interrupt()、處理後續
| 傳統搶佔式思維 | Java 協作式思維 |
|---|---|
| 中斷 = 強制終止 | 中斷 = 請求通知 |
| 隨時可殺死執行緒 | 執行緒決定何時響應 |
| 共享變數需鎖保護 | volatile/interrupted 標記足夠 |
| catch 異常即處理完 | catch 後必須傳遞中斷狀態 |
| 框架不干涉執行緒 | 框架管理生命週期、提供取消 API |
深入理解協作式中斷本質,遵循框架邊界與狀態傳遞準則,是編寫生產級並發應用的必修課。