Java并發(fā)編程之如何優(yōu)雅關閉鉤子Shutdown Hook
關閉鉤子簡介
當程序即將退出時(例如釋放資源、關閉數據庫連接等),可以通過預先注冊一個或多個**關閉鉤子線程(Shutdown Hook)**來執(zhí)行相關操作。當 JVM 進程準備退出時,這些鉤子線程會被觸發(fā)并運行。
示例代碼:
public class HookThreadDemo { privatestaticclass HookRunnable implements Runnable { @Override public void run() { try { System.out.println("鉤子線程 " + Thread.currentThread().getName() + " 正在執(zhí)行..."); Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("鉤子線程 " + Thread.currentThread().getName() + " 執(zhí)行結束"); } } public static void main(String[] args) { HookRunnable hookRunnable = new HookRunnable(); // 添加鉤子線程 0 Runtime.getRuntime().addShutdownHook(new Thread(hookRunnable)); // 添加鉤子線程 1 Runtime.getRuntime().addShutdownHook(new Thread(hookRunnable)); System.out.println("主線程即將結束執(zhí)行"); } }
輸出結果:
主線程即將結束執(zhí)行
鉤子線程 Thread-0 正在執(zhí)行...
鉤子線程 Thread-1 正在執(zhí)行...
鉤子線程 Thread-1 執(zhí)行結束
鉤子線程 Thread-0 執(zhí)行結束
當主線程執(zhí)行完畢后,JVM 進程退出前,所有注冊的鉤子線程會被啟動并執(zhí)行。
關閉鉤子應用場景
釋放資源:關閉文件句柄、數據庫連接等,避免資源泄漏。
停止服務:安全關閉服務器,確保所有請求處理完畢。
發(fā)送通知:通過郵件或短信通知用戶服務已停止。
記錄日志:保存系統(tǒng)狀態(tài)或錯誤信息,便于后續(xù)排查問題。
數據庫連接實戰(zhàn)演示
以下代碼演示如何用關閉鉤子關閉數據庫連接:
public class DatabaseConnection { privatestatic Connection conn; public static void main(String[] args) { System.out.println("主線程開始執(zhí)行"); initConnection(); // 初始化數據庫連接 System.out.println("執(zhí)行數據查詢與處理"); // 注冊關閉鉤子 Runtime.getRuntime().addShutdownHook(new Thread(() -> closeConnection())); System.out.println("主線程結束執(zhí)行"); } private static void initConnection() { try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/school_info?useSSL=true&", "root", "root" ); System.out.println("數據庫連接成功!"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } private static void closeConnection() { try { conn.close(); System.out.println("數據庫連接已關閉!"); } catch (SQLException e) { e.printStackTrace(); } } }
輸出結果:
主線程開始執(zhí)行
數據庫連接成功!
執(zhí)行數據查詢與處理
主線程結束執(zhí)行
數據庫連接已關閉!
使用關閉鉤子的注意事項
- 強制終止進程(如
kill -9
)不會觸發(fā)鉤子線程。 - 避免耗時操作:鉤子線程中不要執(zhí)行長時間任務,否則會延遲 JVM 退出。
- 禁止異常拋出:鉤子線程中的異常可能導致 JVM 無法正常退出。
- 注冊順序:按依賴關系注冊鉤子,先注冊簡單任務,后注冊復雜任務。
- 避免啟動新線程:在鉤子中啟動新線程可能導致 JVM 無法正常關閉。
開源框架中的關閉鉤子機制
1. Spring
在AbstractApplicationContext
中,registerShutdownHook()
方法注冊鉤子,用于關閉上下文:
public void registerShutdownHook() { if (this.shutdownHook == null) { this.shutdownHook = new Thread(() -> doClose()); Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }
2. Tomcat
Tomcat 通過注冊鉤子確保服務關閉時釋放資源:
public void registerShutdownHook() { if (this.shutdownHook == null) { this.shutdownHook = new Thread(() -> { synchronized (startupShutdownMonitor) { doClose(); } }); Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }
關閉鉤子機制的原理
JVM 啟動時,主線程會創(chuàng)建一個關閉線程(Shutdown Thread),并將所有注冊的鉤子添加到其任務列表中。當 JVM 收到終止信號時:
- 停止所有用戶線程。
- 啟動關閉線程,按順序執(zhí)行鉤子任務。
- 等待所有鉤子執(zhí)行完畢或超時后退出。
鉤子的注冊與執(zhí)行
注冊:通過Runtime.getRuntime().addShutdownHook(Thread)
將線程添加到ApplicationShutdownHooks
的靜態(tài)列表中。
執(zhí)行:關閉線程按順序同步執(zhí)行系統(tǒng)級鉤子,異步執(zhí)行應用級鉤子,并等待所有線程完成。
關閉鉤子的觸發(fā)時機
- 主動調用:通過
Runtime.exit()
或System.exit()
觸發(fā)。 - 信號捕獲:JVM 注冊信號處理器(如
INT
、TERM
),捕獲kill
命令發(fā)送的信號后觸發(fā)。
示例代碼(捕獲信號):
public class SignalHandlerTest implements SignalHandler { public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("關閉鉤子正在運行..."))); SignalHandler handler = new SignalHandlerTest(); Signal.handle(new Signal("INT"), handler); // 捕獲 Ctrl+C Signal.handle(new Signal("TERM"), handler); // 捕獲 kill 命令 while (true) { System.out.println("主線程運行中..."); Thread.sleep(2000); } } @Override public void handle(Signal signal) { System.out.println("接收到信號:" + signal.getName() + "-" + signal.getNumber()); System.exit(0); } }
輸出示例:
主線程運行中...
主線程運行中...
^C接收到信號:INT-2
關閉鉤子正在運行...
信號處理與守護線程
信號不可捕獲的情況:KILL
(9)和QUIT
(3)無法被捕獲。
守護線程:JVM 在所有用戶線程結束后自動退出,守護線程(如 GC 線程)不會阻止 JVM 退出。
總結
Java 的關閉鉤子機制覆蓋了大部分退出場景,但以下情況例外:
- 使用
kill -9
強制終止進程時,鉤子不會執(zhí)行。 - 信號處理需調用
System.exit()
確保進程退出。
通過合理使用關閉鉤子,可以實現資源釋放、服務優(yōu)雅關閉等關鍵功能。
到此這篇關于Java并發(fā)編程之如何優(yōu)雅關閉鉤子Shutdown Hook的文章就介紹到這了,更多相關Java關閉鉤子內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java通過調用C/C++實現的DLL動態(tài)庫——JNI的方法
這篇文章主要介紹了Java通過調用C/C++實現的DLL動態(tài)庫——JNI的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-01-01深入探討Spring Statemachine在Spring中實現狀態(tài)機的過程
本文深入探討了Spring Statemachine的核心概念、功能及應用,包括狀態(tài)和轉換的結構化定義、事件驅動的狀態(tài)變遷、持久化支持以及集成Spring生態(tài)系統(tǒng),通過實例分析,展示了其在多個領域的實際應用,并討論了如何自定義擴展以滿足特定需求,感興趣的朋友一起看看吧2025-04-04如何使用Spring Security實現用戶-角色-資源的權限控制
文章介紹了如何通過SpringSecurity實現用戶-角色-資源的權限管理,包括基于角色的請求控制、加載用戶角色信息、角色與資源的關聯(lián)等步驟,同時,提供了一些測試場景,以驗證權限控制是否正確,感興趣的朋友跟隨小編一起看看吧2024-10-10