為什麼 stop()/suspend()/resume() 被棄用
三種正確終止策略對比
| 策略 | 適用場景 | 優點 | 缺點 | 阻塞處理 |
| Run 自然結束 | 短任務、有限迴圈 | 最簡單、最安全 | 無法外部控制 | 不適用 |
| volatile 旗標輪詢 | 非阻塞計算、主動輪詢迴圈 | 實現簡單、響應快 | 阻塞時失效 (sleep/wait/IO) | 需配合 interrupt |
| interrupt() 中斷 | 阻塞操作 (sleep/wait/join/IO/鎖) | 能打破阻塞、標準協議 | 需處理 InterruptedException | 核心機制 |
方案一:volatile 旗標 + interrupt 混合模式 (推薦通用)
public class StoppableTask implements Runnable {
// volatile 保證可見性:寫入立即對其他線程可見
private volatile boolean running = true;
@Override
public void run() {
try {
while (running && !Thread.currentThread().isInterrupted()) {
// 1. 正常業務邏輯
doWork();
// 2. 可選:主動檢查中斷狀態 (非阻塞點)
if (Thread.interrupted()) { // 清除中斷狀態並返回舊值
break;
}
}
} catch (InterruptedException e) {
// 3. 阻塞方法拋出 InterruptedException
Thread.currentThread().interrupt(); // 重置中斷狀態 (重要!)
} finally {
cleanup(); // 釋放資源、關閉連線
}
}
public void stop() {
running = false; // 設置旗標
// 如果線程可能阻塞,必須 interrupt
// 需持有線程引用:thread.interrupt();
}
private void doWork() throws InterruptedException {
// 模擬工作:計算 + 短暫睡眠
// 睡眠會響應 interrupt
Thread.sleep(100);
}
private void cleanup() {
// 關閉流、釋放鎖、歸還連線池
}
}
方案二:標準中斷模式 (阻塞場景專用)
public class InterruptibleTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 業務邏輯
processItem(fetchNextItem());
// 定期主動檢查 (長時間計算時)
// if (Thread.interrupted()) throw new InterruptedException();
}
} catch (InterruptedException e) {
// 正常退出路徑:重置中斷狀態讓上層感知
Thread.currentThread().interrupt();
} catch (Exception e) {
// 異常處理
log.error("任務異常", e);
}
}
private Object fetchNextItem() throws InterruptedException {
// 阻塞隊列 take() 會響應中斷
return blockingQueue.take();
}
}
// 使用方式
Thread worker = new Thread(new InterruptibleTask());
worker.start();
// ... 稍後需要停止
worker.interrupt(); // 唯一標準方式
阻塞場景中斷響應對照表
| 阻塞方法 | 響應 interrupt | 異常/行為 | 備註 |
| Thread.sleep() | ✅ | InterruptedException | 清除中斷狀態 |
| Object.wait() | ✅ | InterruptedException | 清除中斷狀態 |
| Thread.join() | ✅ | InterruptedException | 清除中斷狀態 |
| BlockingQueue.take/put | ✅ | InterruptedException | 清除中斷狀態 |
| ReentrantLock.lockInterruptibly() | ✅ | InterruptedException | 標準鎖不響應 |
| Selector.select() | ✅ | 返回 0, 中斷狀態保留 | NIO 非阻塞 IO |
| InputStream.read() | ❌ (傳統 IO) | 不拋異常 | 需關閉 Socket/Stream 強制釋放 |
| Channel.read() | ✅ (NIO) | ClosedByInterruptException | AsynchronousChannel 異步關閉 |
| Thread.sleep(0) | ✅ | InterruptedException | 可用於主動讓出 CPU 並檢查 |
處理不可中斷阻塞 (傳統 IO、鎖等待)
public class UninterruptibleTask implements Runnable {
private volatile Thread runner;
private final Socket socket;
@Override
public void run() {
runner = Thread.currentThread();
try (InputStream in = socket.getInputStream()) {
byte[] buf = new byte[4096];
while (!Thread.currentThread().isInterrupted()) {
// read() 不響應 interrupt
int len = in.read(buf);
if (len == -1) break;
process(buf, len);
}
} catch (IOException e) {
// Socket 關閉會導致 read 拋出 IOException
if (!Thread.currentThread().isInterrupted()) {
log.error("讀取異常", e);
}
}
}
public void stop() {
// 1. 設置旗標 (邏輯層)
// 2. 強制關閉底層資源 (物理層打破阻塞)
try {
socket.close(); // 導致 read() 拋出 SocketException
} catch (IOException e) {
log.warn("關閉 Socket 失敗", e);
}
// 3. 輔助 interrupt (雙重保險)
if (runner != null) runner.interrupt();
}
}
ExecutorService 优雅關閉模式
ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交任務
Future> future = executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
doWork();
}
});
// 關閉流程
public void shutdownGracefully(ExecutorService executor, long timeout, TimeUnit unit) {
// 1. 拒絕新任務
executor.shutdown();
try {
// 2. 等待現有任務完成
if (!executor.awaitTermination(timeout, unit)) {
// 3. 強制中斷執行中任務
List remaining = executor.shutdownNow(); // 返回未執行任務
log.warn("強制關閉,{} 個任務未完成", remaining.size());
}
} catch (InterruptedException e) {
// 當前線程被中斷
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
最佳實踐總結
- 永遠不使用
stop()、suspend()、resume()
- 首選 interrupt() 作為線程間取消信號
- 捕獲 InterruptedException 後:要么重置中斷狀態
Thread.currentThread().interrupt(),要么拋出/轉換為自訂異常
- 不要吞沒中斷:
catch (InterruptedException e) { } 是嚴重錯誤
- volatile boolean 只能輔助非阻塞點檢查
- 阻塞 IO 使用 NIO (Channels/Selectors) 獲得原生中斷支援
- 資源清理放在 finally:確保鎖、連線、流正確釋放
- 測試中斷場景:單元測試驗證
interrupt() 能正確停止任務