Java并發(fā)編程之如何優(yōu)雅關(guān)閉鉤子Shutdown Hook
關(guān)閉鉤子簡介
當(dāng)程序即將退出時(shí)(例如釋放資源、關(guān)閉數(shù)據(jù)庫連接等),可以通過預(yù)先注冊一個(gè)或多個(gè)**關(guān)閉鉤子線程(Shutdown Hook)**來執(zhí)行相關(guān)操作。當(dāng) JVM 進(jìn)程準(zhǔn)備退出時(shí),這些鉤子線程會被觸發(fā)并運(yùn)行。
示例代碼:
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í)行結(jié)束");
}
}
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("主線程即將結(jié)束執(zhí)行");
}
}輸出結(jié)果:
主線程即將結(jié)束執(zhí)行
鉤子線程 Thread-0 正在執(zhí)行...
鉤子線程 Thread-1 正在執(zhí)行...
鉤子線程 Thread-1 執(zhí)行結(jié)束
鉤子線程 Thread-0 執(zhí)行結(jié)束
當(dāng)主線程執(zhí)行完畢后,JVM 進(jìn)程退出前,所有注冊的鉤子線程會被啟動并執(zhí)行。
關(guān)閉鉤子應(yīng)用場景
釋放資源:關(guān)閉文件句柄、數(shù)據(jù)庫連接等,避免資源泄漏。
停止服務(wù):安全關(guān)閉服務(wù)器,確保所有請求處理完畢。
發(fā)送通知:通過郵件或短信通知用戶服務(wù)已停止。
記錄日志:保存系統(tǒng)狀態(tài)或錯(cuò)誤信息,便于后續(xù)排查問題。
數(shù)據(jù)庫連接實(shí)戰(zhàn)演示
以下代碼演示如何用關(guān)閉鉤子關(guān)閉數(shù)據(jù)庫連接:
public class DatabaseConnection {
privatestatic Connection conn;
public static void main(String[] args) {
System.out.println("主線程開始執(zhí)行");
initConnection(); // 初始化數(shù)據(jù)庫連接
System.out.println("執(zhí)行數(shù)據(jù)查詢與處理");
// 注冊關(guān)閉鉤子
Runtime.getRuntime().addShutdownHook(new Thread(() -> closeConnection()));
System.out.println("主線程結(jié)束執(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("數(shù)據(jù)庫連接成功!");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
private static void closeConnection() {
try {
conn.close();
System.out.println("數(shù)據(jù)庫連接已關(guān)閉!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
主線程開始執(zhí)行
數(shù)據(jù)庫連接成功!
執(zhí)行數(shù)據(jù)查詢與處理
主線程結(jié)束執(zhí)行
數(shù)據(jù)庫連接已關(guān)閉!
使用關(guān)閉鉤子的注意事項(xiàng)
- 強(qiáng)制終止進(jìn)程(如
kill -9)不會觸發(fā)鉤子線程。 - 避免耗時(shí)操作:鉤子線程中不要執(zhí)行長時(shí)間任務(wù),否則會延遲 JVM 退出。
- 禁止異常拋出:鉤子線程中的異??赡軐?dǎo)致 JVM 無法正常退出。
- 注冊順序:按依賴關(guān)系注冊鉤子,先注冊簡單任務(wù),后注冊復(fù)雜任務(wù)。
- 避免啟動新線程:在鉤子中啟動新線程可能導(dǎo)致 JVM 無法正常關(guān)閉。
開源框架中的關(guān)閉鉤子機(jī)制
1. Spring
在AbstractApplicationContext中,registerShutdownHook()方法注冊鉤子,用于關(guān)閉上下文:
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread(() -> doClose());
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}2. Tomcat
Tomcat 通過注冊鉤子確保服務(wù)關(guān)閉時(shí)釋放資源:
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread(() -> {
synchronized (startupShutdownMonitor) {
doClose();
}
});
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}關(guān)閉鉤子機(jī)制的原理
JVM 啟動時(shí),主線程會創(chuàng)建一個(gè)關(guān)閉線程(Shutdown Thread),并將所有注冊的鉤子添加到其任務(wù)列表中。當(dāng) JVM 收到終止信號時(shí):
- 停止所有用戶線程。
- 啟動關(guān)閉線程,按順序執(zhí)行鉤子任務(wù)。
- 等待所有鉤子執(zhí)行完畢或超時(shí)后退出。
鉤子的注冊與執(zhí)行
注冊:通過Runtime.getRuntime().addShutdownHook(Thread)將線程添加到ApplicationShutdownHooks的靜態(tài)列表中。
執(zhí)行:關(guān)閉線程按順序同步執(zhí)行系統(tǒng)級鉤子,異步執(zhí)行應(yīng)用級鉤子,并等待所有線程完成。
關(guān)閉鉤子的觸發(fā)時(shí)機(jī)
- 主動調(diào)用:通過
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("關(guān)閉鉤子正在運(yùn)行...")));
SignalHandler handler = new SignalHandlerTest();
Signal.handle(new Signal("INT"), handler); // 捕獲 Ctrl+C
Signal.handle(new Signal("TERM"), handler); // 捕獲 kill 命令
while (true) {
System.out.println("主線程運(yùn)行中...");
Thread.sleep(2000);
}
}
@Override
public void handle(Signal signal) {
System.out.println("接收到信號:" + signal.getName() + "-" + signal.getNumber());
System.exit(0);
}
}輸出示例:
主線程運(yùn)行中...
主線程運(yùn)行中...
^C接收到信號:INT-2
關(guān)閉鉤子正在運(yùn)行...
信號處理與守護(hù)線程
信號不可捕獲的情況:KILL(9)和QUIT(3)無法被捕獲。
守護(hù)線程:JVM 在所有用戶線程結(jié)束后自動退出,守護(hù)線程(如 GC 線程)不會阻止 JVM 退出。
總結(jié)
Java 的關(guān)閉鉤子機(jī)制覆蓋了大部分退出場景,但以下情況例外:
- 使用
kill -9強(qiáng)制終止進(jìn)程時(shí),鉤子不會執(zhí)行。 - 信號處理需調(diào)用
System.exit()確保進(jìn)程退出。
通過合理使用關(guān)閉鉤子,可以實(shí)現(xiàn)資源釋放、服務(wù)優(yōu)雅關(guān)閉等關(guān)鍵功能。
到此這篇關(guān)于Java并發(fā)編程之如何優(yōu)雅關(guān)閉鉤子Shutdown Hook的文章就介紹到這了,更多相關(guān)Java關(guān)閉鉤子內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java通過調(diào)用C/C++實(shí)現(xiàn)的DLL動態(tài)庫——JNI的方法
這篇文章主要介紹了Java通過調(diào)用C/C++實(shí)現(xiàn)的DLL動態(tài)庫——JNI的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01
深入探討Spring Statemachine在Spring中實(shí)現(xiàn)狀態(tài)機(jī)的過程
本文深入探討了Spring Statemachine的核心概念、功能及應(yīng)用,包括狀態(tài)和轉(zhuǎn)換的結(jié)構(gòu)化定義、事件驅(qū)動的狀態(tài)變遷、持久化支持以及集成Spring生態(tài)系統(tǒng),通過實(shí)例分析,展示了其在多個(gè)領(lǐng)域的實(shí)際應(yīng)用,并討論了如何自定義擴(kuò)展以滿足特定需求,感興趣的朋友一起看看吧2025-04-04
Springboot項(xiàng)目中定時(shí)任務(wù)的四種實(shí)現(xiàn)方式詳解
Spring的@Scheduled注解是一種非常簡單和便捷的實(shí)現(xiàn)定時(shí)任務(wù)的方式,通過在方法上添加@Scheduled注解,我們可以指定方法在特定的時(shí)間間隔或固定的時(shí)間點(diǎn)執(zhí)行,本文給大家介紹Springboot項(xiàng)目中定時(shí)任務(wù)的四種實(shí)現(xiàn)方式,感興趣的的朋友一起看看b2024-02-02
java 分割csv數(shù)據(jù)的實(shí)例詳解
這篇文章主要介紹了java 分割csv數(shù)據(jù)的實(shí)例詳解的相關(guān)資料,這里提供了簡單實(shí)例,需要的朋友可以參考下2017-07-07
深入理解Java中Filter的作用種類及應(yīng)用場景
Filter(過濾器)是Java Web中的一種重要組件,可以對請求和響應(yīng)進(jìn)行攔截處理,對數(shù)據(jù)進(jìn)行過濾和處理。Filter可以實(shí)現(xiàn)許多功能,如:鑒權(quán)、日志記錄、字符編碼轉(zhuǎn)換、數(shù)據(jù)壓縮、請求重定向等等2023-04-04
如何使用Spring Security實(shí)現(xiàn)用戶-角色-資源的權(quán)限控制
文章介紹了如何通過SpringSecurity實(shí)現(xiàn)用戶-角色-資源的權(quán)限管理,包括基于角色的請求控制、加載用戶角色信息、角色與資源的關(guān)聯(lián)等步驟,同時(shí),提供了一些測試場景,以驗(yàn)證權(quán)限控制是否正確,感興趣的朋友跟隨小編一起看看吧2024-10-10
spring boot 測試單元修改數(shù)據(jù)庫不成功的解決
這篇文章主要介紹了spring boot 測試單元修改數(shù)據(jù)庫不成功的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

