Thread.stop()、suspend()、resume() 自 JDK 1.2 起被標記為 @Deprecated,原因是它們強制釋放鎖、破壞對象不變量、導致數據競態與死鎖。正確的線程關閉必須遵循協作式原則:線程主動檢查關閉信號、清理資源、有序退出。
本文系統梳理三大核心模式,並給出生產級代碼模板。
public class FlagTask extends Thread {
// volatile 保證跨線程可見性,防止 JIT 將標誌快取寄存器導致不可見
private volatile boolean running = true;
public void shutdown() {
running = false;
}
@Override
public void run() {
while (running) {
// 執行非阻塞業務邏輯
doWork();
}
// 清理資源、記錄日誌
cleanup();
}
private void doWork() { /* 計算、輪詢、發送心跳等 */ }
private void cleanup() { /* 關閉連接、刷新緩衝區 */ }
}
| 優點 | 缺點 | 適用 |
|---|---|---|
| 簡單、無異常、響應即時 | 無法中斷阻塞調用(sleep, wait, I/O, Lock) | 純計算循環、輪詢任務、非阻塞隊列消費 |
private final AtomicBoolean running = new AtomicBoolean(true);
public void shutdown() { running.set(false); }
while (running.get()) { ... }
語義更清晰,支援 CAS 操作(如 compareAndSet(true, false) 實現「只關閉一次」)。
Thread.interrupt() 設置該標誌為 true。Object.wait()、Thread.sleep()、Thread.join()、Selector.select()、InterruptibleChannel 操作,這些方法會拋出 InterruptedException 並清除中斷標誌。Thread.interrupted()(檢查並清除)或 isInterrupted()(僅檢查)。public class InterruptibleTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 1. 非阻塞工作
doWork();
// 2. 可中斷阻塞點(sleep/wait/阻塞隊列 take)
Thread.sleep(1000); // 或 blockingQueue.take()
}
} catch (InterruptedException e) {
// 3. 捕獲中斷異常 → 確認關閉意圖 → 清理 → 可選:重新設置中斷標誌傳播
Thread.currentThread().interrupt(); // 重要:恢復中斷狀態給上層調用者
} finally {
cleanup(); // 釋放鎖、關閉連接、刷新數據
}
}
// ...
}
Thread.currentThread().interrupt() 重新設置標誌,或拋出異常向上傳播。否則中斷信號丟失,上層無法感知。isInterrupted();阻塞調用依賴異常拋出。Lock、Condition、CompletableFuture)底層用 LockSupport.park(),同樣響應中斷(返回而不拋異常,需自檢 Thread.interrupted())。傳統 BIO (InputStream.read, ServerSocket.accept) 不響應中斷。解決方案見模式三。
關閉線程持有的底層資源(Socket、Channel、鎖),導致阻塞調用拋出異常(SocketException: Socket closed、AsynchronousCloseException、ClosedByInterruptException),從而強制線程從阻塞中返回。
public class SocketServerTask implements Runnable {
private volatile ServerSocket serverSocket;
@Override
public void run() {
try {
serverSocket = new ServerSocket(3333);
while (!Thread.currentThread().isInterrupted()) {
Socket client = serverSocket.accept(); // 阻塞點
handleClient(client);
}
} catch (IOException e) {
// 正常關閉路徑:Socket closed / AsynchronousCloseException
if (!(e instanceof SocketException && e.getMessage().contains("Socket closed"))) {
e.printStackTrace(); // 真正錯誤
}
} finally {
closeQuietly(serverSocket);
}
}
public void shutdown() {
closeQuietly(serverSocket); // 觸發 accept() 拋異常
}
private void closeQuietly(Closeable c) {
if (c != null) try { c.close(); } catch (IOException ignored) {}
}
}
// Selector 可被 interrupt() 喚醒,或 close() 關閉
Selector selector = Selector.open();
selector.wakeup(); // 或 selector.close()
// Statement.cancel() / Connection.close() 可中斷正在執行的查詢
statement.cancel(); // 標準 JDBC 4.0+
connection.close();
| 維度 | volatile 旗標 | interrupt() | 資源關閉 |
|---|---|---|---|
| 阻塞類型 | 無阻塞 / 非阻塞隊列 | 可中斷阻塞 (sleep, wait, park, NIO, Lock) | 不可中斷阻塞 (BIO I/O, synchronized, 外部庫) |
| 響應延遲 | 下次循環檢查 | 立即 (拋異常) 或下次檢查點 | 立即 (底層資源關閉觸發異常) |
| 代碼侵入性 | 低 (while 判斷) | 中 (try-catch, 恢復標誌) | 高 (需持有資源引用, 區分正常/異常關閉) |
| 線程安全 | volatile 保證可見性 | JVM 內建原子操作 | 需同步資源訪問 |
| 組合使用 | ✅ 基礎 | ✅ 核心 | ✅ 最後手段 |
sleep/wait/join/park/NIO/Lock → interrupt() + 旗標雙重保險。public abstract class GracefulTask implements Runnable {
private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
private volatile Thread runner;
public final void start() {
runner = new Thread(this, getName());
runner.start();
}
public final void shutdown() {
if (shutdownRequested.compareAndSet(false, true)) {
Thread t = runner;
if (t != null) {
t.interrupt(); // 嘗試中斷
closeResources(); // 關閉底層資源 (Socket, Channel, Selector, Connection)
}
}
}
protected abstract void closeResources(); // 子類實現具體資源關閉
@Override
public final void run() {
try {
execute(); // 子類實現業務邏輯
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢復標誌
} catch (Exception e) {
handleError(e);
} finally {
cleanup();
}
}
protected abstract void execute() throws InterruptedException;
protected void handleError(Exception e) { log.error("Task error", e); }
protected void cleanup() { /* 統一清理 */ }
// 供 execute() 中長循環調用
protected final boolean shouldRun() {
return !shutdownRequested.get() && !Thread.currentThread().isInterrupted();
}
}
// StructuredTaskScope (Java 21 Preview)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> f1 = scope.fork(() -> fetchUrl(url1));
Future<Integer> f2 = scope.fork(() -> compute());
scope.join(); // 等待所有完成
scope.throwIfFailed(); // 任一失敗拋異常
String result1 = f1.resultNow();
int result2 = f2.resultNow();
} // 作用域結束自動關閉所有子任務 (interrupt + 關閉資源)
結構化並發將「生命週期綁定到代碼塊」作為語言級特性,徹底解決線程洩漏、關閉傳播難題,是未來標準。
Java 線程關閉的本質是協作式取消。沒有銀彈,只有根據阻塞類型選擇volatile 旗標、interrupt()、資源關閉的組合拳。現代開發應:
Thread.stop()、suspend()、吞噬 InterruptedException。掌握這三種模式,讓你的多線程系統在部署、升級、故障恢復時都能體面地「說再見」。