Apache Ignite 中用于 安全、可靠地關閉線程池(ExecutorService
) 的關鍵邏輯。我們來一步步深入理解它的設計思想和實現細節。
🧱 一、核心方法:U.shutdownNow(...)
public static void shutdownNow(Class<?> owner, @Nullable ExecutorService exec, @Nullable IgniteLogger log
)
? 功能:
安全地關閉一個
ExecutorService
,并等待它完全停止。
🔍 參數說明:
參數 | 含義 |
---|---|
owner | 誰創建了這個線程池(用于日志記錄) |
exec | 要關閉的線程池(可能為 null ) |
log | 日志組件(可能為 null ) |
🚀 方法執行流程
1. 判空保護
if (exec != null) { ... }
- 如果線程池是
null
,直接跳過 —— 避免空指針異常
2. 立即關閉:shutdownNow()
List<Runnable> tasks = exec.shutdownNow();
shutdownNow()
會:- 嘗試 中斷所有正在運行的工作線程
- 返回 尚未執行的任務列表(隊列中的任務)
?? 注意:它不保證正在運行的任務會被中斷成功(比如任務中捕獲了
InterruptedException
或未響應中斷)
3. 檢查是否有“幸存任務”
if (!F.isEmpty(tasks))U.warn(log, "Runnable tasks outlived thread pool executor service [...]");
F.isEmpty(...)
是 Ignite 的工具方法,判斷集合是否為空- 如果返回的任務非空 → 說明有些任務還沒來得及執行就被“拋棄”了
- 打印警告日志,包含:
- 線程池所有者(
owner
) - 未執行的任務列表(幫助排查問題)
- 線程池所有者(
📌 這是非常重要的 可觀測性設計:告訴你“哪些任務丟了”
4. 等待線程池終止
try {exec.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
catch (InterruptedException ignored) {warn(log, "Got interrupted while waiting for executor service to stop.");exec.shutdownNow(); // 再次嘗試Thread.currentThread().interrupt(); // 恢復中斷標志
}
🔹 awaitTermination(...)
的作用:
- 阻塞當前線程,直到線程池真正終止
- 使用
Long.MAX_VALUE
表示“無限等待” → 確保一定等到為止
🔹 為什么捕獲 InterruptedException
?
- 在等待過程中,當前線程可能被其他線程中斷
- 我們不能“吞掉”這個中斷信號(否則上層邏輯可能無法感知中斷)
- 所以:
- 打個日志
- 再調一次
shutdownNow()
(加強關閉力度) - 恢復中斷狀態:
Thread.currentThread().interrupt()
? 這是 Java 多線程編程中的 最佳實踐:不丟失中斷信號
🔄 二、封裝調用:stopExecutors(...)
和 stopExecutors0(...)
private void stopExecutors(IgniteLogger log) {boolean interrupted = Thread.interrupted();try {stopExecutors0(log);}finally {if (interrupted)Thread.currentThread().interrupt();}
}
? 為什么要兩層?
這是為了 正確處理線程中斷狀態。
🔍 詳細解釋:
Thread.interrupted()
:
- 是一個靜態方法
- 返回當前線程是否被中斷,并清除中斷標志
所以:
boolean interrupted = Thread.interrupted();
→ 拿到中斷狀態后,中斷標志就被清除了。
在 finally
塊中:
if (interrupted)Thread.currentThread().interrupt();
→ 如果之前是中斷的,就重新設置中斷標志
? 目的:不改變外部線程的中斷狀態,即“來的時候什么樣,走的時候還什么樣”
stopExecutors0(...)
:真正干活的方法
private void stopExecutors0(IgniteLogger log) {assert log != null;U.shutdownNow(getClass(), snpExecSvc, log);// 可能還有其他線程池...
}
- 斷言日志不為空
- 調用
U.shutdownNow
關閉snpExecSvc
(可能是 striped executor 或其他線程池) - 可能還會關閉其他線程池(如 IO、查詢、系統任務等)
🎯 三、整體作用總結
目標 | 實現方式 |
---|---|
安全關閉線程池 | shutdownNow() + awaitTermination() |
防止任務丟失 | 檢查 shutdownNow() 返回的任務,打警告日志 |
避免中斷信號丟失 | 保存并恢復中斷狀態 |
增強可觀測性 | 記錄線程池所有者、未執行任務 |
健壯性 | 空值保護、異常處理、重復嘗試 |
🧩 四、類比理解
想象你在關閉一家快遞分揀中心:
- 廣播通知:“所有人立刻停止工作!” →
shutdownNow()
- 清點包裹:看看還有多少包裹沒來得及處理 →
List<Runnable> tasks
- 上報丟失件:如果有未處理的包裹,上報給管理層 → 打日志
- 等員工全部離崗:你在門口等著,直到最后一個員工離開 →
awaitTermination
- 遇到警察攔路(中斷):你被打斷等待,但你記得自己還沒關完門 → 恢復中斷標志
? 五、最佳實踐提煉(可用于你自己的項目)
public static void safeShutdown(ExecutorService exec, String name, Logger log) {if (exec == null) return;// 1. 嘗試立即關閉List<Runnable> remainingTasks = exec.shutdownNow();// 2. 報告未執行任務if (!remainingTasks.isEmpty()) {log.warn("Tasks lost during shutdown of {}: {}", name, remainingTasks.size());}// 3. 等待終止try {if (!exec.awaitTermination(30, TimeUnit.SECONDS)) {log.warn("Executor {} didn't terminate in time, forcing...");exec.shutdownNow(); // 再試一次}} catch (InterruptedException e) {log.warn("Shutdown interrupted, forcing shutdown.");exec.shutdownNow();Thread.currentThread().interrupt(); // 恢復中斷}
}
💡 你可以根據需要調整超時時間(比如不用
Long.MAX_VALUE
)
📌 六、一句話總結
U.shutdownNow(...)
是一個 生產級的線程池關閉工具方法,它不僅嘗試關閉線程池,還關注 任務是否丟失、線程是否真正退出、中斷信號是否保留,體現了 Ignite 對 穩定性、可觀測性和健壯性 的高度重視。
如果你在開發中間件、框架或高可用系統,這種關閉模式 必須掌握并復用。