上一篇介紹了 volatile 標志位與 interrupt() 基礎用法。本文深入剖析 Java 中斷機制的底層語義、阻塞方法的響應行為、以及構建健壯取消邏輯的完整模式。
Java 不提供搶佔式安全終止線程的 API(如已棄用的 stop())。取而代之的是協作式中斷機制:
interrupt() 將狀態置 true,不會立即停止線程,只是發出「請求中斷」信號。sleep()、wait()、BlockingQueue.take())檢測到狀態,拋出 InterruptedException 並清除中斷狀態。
// 生產者速度遠快於消費者,隊列滿時 put() 阻塞
while (on) { // volatile boolean on = true;
queue.put(item); // 阻塞在此,無法檢查 on == false
}
cancel() { on = false; } // 設置後,生產者仍卡在 put(),線程永不結束
結論:單純 volatile 標志位無法中斷阻塞在 I/O、鎖、隊列操作中的線程。
public void run() {
try {
while (on && !Thread.currentThread().isInterrupted()) {
queue.put(item); // 可中斷阻塞方法
}
} catch (InterruptedException e) {
// 關鍵:恢復中斷狀態,讓上層調用者可感知
Thread.currentThread().interrupt();
return; // 或拋出異常
}
}
public void cancel() {
on = false;
interrupt(); // 喚醒阻塞線程
}
關鍵點:
on && !isInterrupted()。InterruptedException 後 必須恢復中斷狀態(Thread.currentThread().interrupt()),否則上層調用者無法感知中斷請求。interrupt() 打破阻塞。
Future<?> task = executor.submit(runnable);
try {
task.get(5, TimeUnit.SECONDS); // 超時取消
} catch (TimeoutException e) {
task.cancel(true); // true = mayInterruptIfRunning
} finally {
// 清理資源
}
cancel(true):若任務正在執行,發送中斷;若未啟動,不再啟動。cancel(false):僅不啟動未執行任務,不中斷正在運行的。場景:多生產者寫入隊列,單消費者異步落盤。需保證 關閉時已提交日志全部寫入,且無新日志接入。
public class LoggerService {
private final BlockingQueue<String> logQueue = new LinkedBlockingQueue<>(5);
private final LoggerThread loggerThread;
private volatile boolean isShutdown = false;
private int reservations = 0; // 正在處理的生產者計數
public void recordLog(String msg) throws InterruptedException {
synchronized (this) {
if (isShutdown) throw new IllegalStateException("已關閉");
++reservations;
}
logQueue.put(msg); // 阻塞直到有空位
}
public void stop() {
synchronized (this) { isShutdown = true; }
loggerThread.interrupt(); // 打破 take() 阻塞
}
private class LoggerThread extends Thread {
public void run() {
try {
while (true) {
String msg;
try {
synchronized (LoggerService.this) {
if (isShutdown && reservations == 0) break; // 雙重檢查
}
msg = logQueue.take(); // 可中斷阻塞
synchronized (LoggerService.this) { --reservations; }
writer.println("Read: " + msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢復中斷狀態
}
}
} finally { writer.close(); }
}
}
}
設計亮點:
reservations 計數器確保「關閉請求發出後,不再接受新日志,且等待在途日志處理完畢」。recordLog 先原子檢查關閉標志再增加計數,避免競態。interrupt() + InterruptedException)而非單純標志位。BlockingQueue、Thread.sleep、Lock.lockInterruptibly 等)。InterruptedException 後 恢復中斷狀態 或 拋出異常,切勿吞噬(printStackTrace() 即吞噬)。Future.cancel(true) / 線程池 shutdownNow() 統一管理任務生命週期。