电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Java Shutdown Hook場景使用及源碼分析

瀏覽:2日期:2022-08-10 15:04:23
目錄背景Shutdown Hook 介紹關閉鉤子被調用場景注意事項實踐Shutdown Hook 在 Spring 中的運用背景

如果想在 Java 進程退出時,包括正常和異常退出,做一些額外處理工作,例如資源清理,對象銷毀,內存數據持久化到磁盤,等待線程池處理完所有任務等等。特別是進程異常掛掉的情況,如果一些重要狀態沒及時保留下來,或線程池的任務沒被處理完,有可能會造成嚴重問題。那該怎么辦呢?

Java 中的 Shutdown Hook 提供了比較好的方案。我們可以通過 Java.Runtime.addShutdownHook(Thread hook) 方法向 JVM 注冊關閉鉤子,在 JVM 退出之前會自動調用執行鉤子方法,做一些結尾操作,從而讓進程平滑優雅的退出,保證了業務的完整性。

Shutdown Hook 介紹

其實,shutdown hook 就是一個簡單的已初始化但是未啟動的線程。當虛擬機開始關閉時,它將會調用所有已注冊的鉤子,這些鉤子執行是并發的,執行順序是不確定的。

在虛擬機關閉的過程中,還可以繼續注冊新的鉤子,或者撤銷已經注冊過的鉤子。不過有可能會拋出 IllegalStateException。注冊和注銷鉤子的方法定義如下:

public void addShutdownHook(Thread hook) { // 省略}public void removeShutdownHook(Thread hook) { // 省略}關閉鉤子被調用場景

關閉鉤子可以在以下幾種場景被調用:

程序正常退出 程序調用 System.exit() 退出 終端使用 Ctrl+C 中斷程序 程序拋出異常導致程序退出,例如 OOM,數組越界等異常 系統事件,例如用戶注銷或關閉系統 使用 Kill pid 命令殺掉進程,注意使用 kill -9 pid 強制殺掉不會觸發執行鉤子

驗證程序正常退出情況

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法...'))); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');Thread.sleep(2000);System.out.println('程序即將退出...'); }}

運行結果

程序開始啟動...程序即將退出...執行鉤子方法...

Process finished with exit code 0

驗證程序調用 System.exit() 退出情況

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法...'))); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');Thread.sleep(2000);System.exit(-1);System.out.println('程序即將退出...'); }}

運行結果

程序開始啟動...執行鉤子方法...

Process finished with exit code -1

驗證終端使用 Ctrl+C 中斷程序,在命令行窗口中運行程序,然后使用 Ctrl+C 中斷

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法...'))); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');Thread.sleep(2000);System.out.println('程序即將退出...'); }}

運行結果

D:IdeaProjectsjava-demojava ShutdownHookDemo程序開始啟動...執行鉤子方法...

演示拋出異常導致程序異常退出

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法...'))); } public static void main(String[] args) {System.out.println('程序開始啟動...');int a = 0;System.out.println(10 / a);System.out.println('程序即將退出...'); }}

運行結果

程序開始啟動...執行鉤子方法...Exception in thread 'main' java.lang.ArithmeticException: / by zero at com.chenpi.ShutdownHookDemo.main(ShutdownHookDemo.java:12)

Process finished with exit code 1

至于系統被關閉,或者使用 Kill pid 命令殺掉進程就不演示了,感興趣的可以自行驗證。

注意事項

可以向虛擬機注冊多個關閉鉤子,但是注意這些鉤子執行是并發的,執行順序是不確定的。

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法A...')));Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法B...')));Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法C...'))); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');Thread.sleep(2000);System.out.println('程序即將退出...'); }}

運行結果

程序開始啟動...程序即將退出...執行鉤子方法B...執行鉤子方法C...執行鉤子方法A...

向虛擬機注冊的鉤子方法需要盡快執行結束,盡量不要執行長時間的操作,例如 I/O 等可能被阻塞的操作,死鎖等,這樣就會導致程序短時間不能被關閉,甚至一直關閉不了。我們也可以引入超時機制強制退出鉤子,讓程序正常結束。

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> { // 模擬長時間的操作 try {Thread.sleep(1000000); } catch (InterruptedException e) {e.printStackTrace(); }})); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');Thread.sleep(2000);System.out.println('程序即將退出...'); }}

以上的鉤子執行時間比較長,最終會導致程序在等待很長時間之后才能被關閉。

如果 JVM 已經調用執行關閉鉤子的過程中,不允許注冊新的鉤子和注銷已經注冊的鉤子,否則會報 IllegalStateException 異常。通過源碼分析,JVM 調用鉤子的時候,即調用 ApplicationShutdownHooks#runHooks() 方法,會將所有鉤子從變量 hooks 取出,然后將此變量置為 null。

// 調用執行鉤子static void runHooks() { Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) {threads = hooks.keySet();hooks = null; } for (Thread hook : threads) {hook.start(); } for (Thread hook : threads) {try { hook.join();} catch (InterruptedException x) { } }}

在注冊和注銷鉤子的方法中,首先會判斷 hooks 變量是否為 null,如果為 null 則拋出異常。

// 注冊鉤子static synchronized void add(Thread hook) { if(hooks == null)throw new IllegalStateException('Shutdown in progress'); if (hook.isAlive())throw new IllegalArgumentException('Hook already running'); if (hooks.containsKey(hook))throw new IllegalArgumentException('Hook previously registered'); hooks.put(hook, hook);}// 注銷鉤子static synchronized boolean remove(Thread hook) { if(hooks == null)throw new IllegalStateException('Shutdown in progress'); if (hook == null)throw new NullPointerException(); return hooks.remove(hook) != null;}

我們演示下這種情況

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println('執行鉤子方法...'); Runtime.getRuntime().addShutdownHook(new Thread( () -> System.out.println('在JVM調用鉤子的過程中再新注冊鉤子,會報錯IllegalStateException'))); // 在JVM調用鉤子的過程中注銷鉤子,會報錯IllegalStateException Runtime.getRuntime().removeShutdownHook(Thread.currentThread());})); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');Thread.sleep(2000);System.out.println('程序即將退出...'); }}

運行結果

程序開始啟動...程序即將退出...執行鉤子方法...Exception in thread 'Thread-0' java.lang.IllegalStateException: Shutdown in progress at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:66) at java.lang.Runtime.addShutdownHook(Runtime.java:211) at com.chenpi.ShutdownHookDemo.lambda$static$1(ShutdownHookDemo.java:8) at java.lang.Thread.run(Thread.java:748)

如果調用 Runtime.getRuntime().halt() 方法停止 JVM,那么虛擬機是不會調用鉤子的。

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println('執行鉤子方法...'))); } public static void main(String[] args) {System.out.println('程序開始啟動...');System.out.println('程序即將退出...');Runtime.getRuntime().halt(0); }}

運行結果

程序開始啟動...程序即將退出...

Process finished with exit code 0

如果要想終止執行中的鉤子方法,只能通過調用 Runtime.getRuntime().halt() 方法,強制讓程序退出。在Linux環境中使用 kill -9 pid 命令也是可以強制終止退出。

package com.chenpi;public class ShutdownHookDemo { static {Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println('開始執行鉤子方法...'); Runtime.getRuntime().halt(-1); System.out.println('結束執行鉤子方法...');})); } public static void main(String[] args) {System.out.println('程序開始啟動...');System.out.println('程序即將退出...'); }}

運行結果

程序開始啟動...程序即將退出...開始執行鉤子方法...

Process finished with exit code -1

如果程序使用 Java Security Managers,使用 shutdown Hook 則需要安全權限 RuntimePermission(“shutdownHooks”),否則會導致 SecurityException。

實踐

例如,我們程序自定義了一個線程池,用來接收和處理任務。如果程序突然奔潰異常退出,這時線程池的所有任務有可能還未處理完成,如果不處理完程序就直接退出,可能會導致數據丟失,業務異常等重要問題。這時鉤子就派上用場了。

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class ShutdownHookDemo { // 線程池 private static ExecutorService executorService = Executors.newFixedThreadPool(3); static {Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println('開始執行鉤子方法...'); // 關閉線程池 executorService.shutdown(); try { // 等待60秒System.out.println(executorService.awaitTermination(60, TimeUnit.SECONDS)); } catch (InterruptedException e) {e.printStackTrace(); } System.out.println('結束執行鉤子方法...');})); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動...');// 向線程池添加10個任務for (int i = 0; i < 10; i++) { Thread.sleep(1000); final int finalI = i; executorService.execute(() -> {try { Thread.sleep(4000);} catch (InterruptedException e) { e.printStackTrace();}System.out.println('Task ' + finalI + ' execute...'); }); System.out.println('Task ' + finalI + ' is in thread pool...');} }}

在命令行窗口中運行程序,在10個任務都提交到線程池之后,任務都還未處理完成之前,使用 Ctrl+C 中斷程序,最終在虛擬機關閉之前,調用了關閉鉤子,關閉線程池,并且等待60秒讓所有任務執行完成。

Java Shutdown Hook場景使用及源碼分析

Shutdown Hook 在 Spring 中的運用

Shutdown Hook 在 Spring 中是如何運用的呢。通過源碼分析,Springboot 項目啟動時會判斷 registerShutdownHook 的值是否為 true,默認是 true,如果為真則向虛擬機注冊關閉鉤子。

private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } }}@Overridepublic void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() { @Override public void run() { synchronized (startupShutdownMonitor) {// 鉤子方法 doClose(); } } }; // 底層還是使用此方法注冊鉤子 Runtime.getRuntime().addShutdownHook(this.shutdownHook); }}

在關閉鉤子的方法 doClose 中,會做一些虛擬機關閉前處理工作,例如銷毀容器里所有單例 Bean,關閉 BeanFactory,發布關閉事件等等。

protected void doClose() { // Check whether an actual close attempt is necessary... if (this.active.get() && this.closed.compareAndSet(false, true)) { if (logger.isDebugEnabled()) { logger.debug('Closing ' + this); } LiveBeansView.unregisterApplicationContext(this); try { // 發布Spring 應用上下文的關閉事件,讓監聽器在應用關閉之前做出響應處理 publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn('Exception thrown from ApplicationListener handling ContextClosedEvent', ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. if (this.lifecycleProcessor != null) { try { // 執行lifecycleProcessor的關閉方法 this.lifecycleProcessor.onClose(); } catch (Throwable ex) { logger.warn('Exception thrown from LifecycleProcessor on context close', ex); } } // 銷毀容器里所有單例Bean destroyBeans(); // 關閉BeanFactory closeBeanFactory(); // Let subclasses do some final clean-up if they wish... onClose(); // Reset local application listeners to pre-refresh state. if (this.earlyApplicationListeners != null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // Switch to inactive. this.active.set(false); }}

我們知道,我們可以定義 bean 并且實現 DisposableBean 接口,重寫 destroy 對象銷毀方法。destroy 方法就是在 Spring 注冊的關閉鉤子里被調用的。例如我們使用 Spring 框架的 ThreadPoolTaskExecutor 線程池類,它就實現了 DisposableBean 接口,重寫了 destroy 方法,從而在程序退出前,進行線程池銷毀工作。源碼如下:

@Overridepublic void destroy() { shutdown();}/** * Perform a shutdown on the underlying ExecutorService. * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#shutdownNow() */public void shutdown() { if (logger.isInfoEnabled()) { logger.info('Shutting down ExecutorService' + (this.beanName != null ? ' ’' + this.beanName + '’' : '')); } if (this.executor != null) { if (this.waitForTasksToCompleteOnShutdown) { this.executor.shutdown(); } else { for (Runnable remainingTask : this.executor.shutdownNow()) { cancelRemainingTask(remainingTask); } } awaitTerminationIfNecessary(this.executor); }}

到此這篇關于Java Shutdown Hook場景使用及源碼分析的文章就介紹到這了,更多相關Java Shutdown Hook內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Java
相關文章:
主站蜘蛛池模板: 盐水蒸发器,水洗盐设备,冷凝结晶切片机,转鼓切片机,絮凝剂加药系统-无锡瑞司恩机械有限公司 | 超声波清洗机_大型超声波清洗机_工业超声波清洗设备-洁盟清洗设备 | 考勤系统_人事考勤管理系统_本地部署BS考勤系统_考勤软件_天时考勤管理专家 | 网络推广公司_网络营销方案策划_企业网络推广外包平台-上海澜推网络 | 湿地保护| 柴油发电机组_柴油发电机_发电机组价格-江苏凯晨电力设备有限公司 | 北京租车公司_汽车/客车/班车/大巴车租赁_商务会议/展会用车/旅游大巴出租_北京桐顺创业租车公司 | 翻斗式矿车|固定式矿车|曲轨侧卸式矿车|梭式矿车|矿车配件-山东卓力矿车生产厂家 | 北京翻译公司_同传翻译_字幕翻译_合同翻译_英语陪同翻译_影视翻译_翻译盖章-译铭信息 | 青岛球场围网,青岛车间隔离网,青岛机器人围栏,青岛水源地围网,青岛围网,青岛隔离栅-青岛晟腾金属制品有限公司 | 对夹式止回阀_对夹式蝶形止回阀_对夹式软密封止回阀_超薄型止回阀_不锈钢底阀-温州上炬阀门科技有限公司 | 铣刨料沥青破碎机-沥青再生料设备-RAP热再生混合料破碎筛分设备 -江苏锡宝重工 | 九爱图纸|机械CAD图纸下载交流中心 | 非标压力容器_碳钢储罐_不锈钢_搪玻璃反应釜厂家-山东首丰智能环保装备有限公司 | 长城人品牌官网| 纯水电导率测定仪-万用气体检测仪-低钠测定仪-米沃奇科技(北京)有限公司www.milwaukeeinst.cn 锂辉石检测仪器,水泥成分快速分析仪-湘潭宇科分析仪器有限公司 手术室净化装修-手术室净化工程公司-华锐手术室净化厂家 | 臭氧发生器_臭氧消毒机 - 【同林品牌 实力厂家】 | 工业洗衣机_工业洗涤设备_上海力净工业洗衣机厂家-洗涤设备首页 bkzzy在职研究生网 - 在职研究生招生信息咨询平台 | 蔬菜配送公司|蔬菜配送中心|食材配送|饭堂配送|食堂配送-首宏公司 | 礼仪庆典公司,礼仪策划公司,庆典公司,演出公司,演艺公司,年会酒会,生日寿宴,动工仪式,开工仪式,奠基典礼,商务会议,竣工落成,乔迁揭牌,签约启动-东莞市开门红文化传媒有限公司 | 杭州中策电线|中策电缆|中策电线|杭州中策电缆|杭州中策电缆永通集团有限公司 | RTO换向阀_VOC高温阀门_加热炉切断阀_双偏心软密封蝶阀_煤气蝶阀_提升阀-湖北霍科德阀门有限公司 | 海外整合营销-独立站营销-社交媒体运营_广州甲壳虫跨境网络服务 焊管生产线_焊管机组_轧辊模具_焊管设备_焊管设备厂家_石家庄翔昱机械 | 洛阳防爆合格证办理-洛阳防爆认证机构-洛阳申请国家防爆合格证-洛阳本安防爆认证代办-洛阳沪南抚防爆电气技术服务有限公司 | 车充外壳,车载充电器外壳,车载点烟器外壳,点烟器连接头,旅行充充电器外壳,手机充电器外壳,深圳市华科达塑胶五金有限公司 | 焊接烟尘净化器__焊烟除尘设备_打磨工作台_喷漆废气治理设备 -催化燃烧设备 _天津路博蓝天环保科技有限公司 | 电磁流量计厂家_涡街流量计厂家_热式气体流量计-青天伟业仪器仪表有限公司 | 铝扣板-铝方通-铝格栅-铝条扣板-铝单板幕墙-佳得利吊顶天花厂家 elisa试剂盒价格-酶联免疫试剂盒-猪elisa试剂盒-上海恒远生物科技有限公司 | 外贸网站建设-外贸网站设计制作开发公司-外贸独立站建设【企术】 | 衬四氟_衬氟储罐_四氟储罐-无锡市氟瑞特防腐科技有限公司 | 氢氧化钙设备_厂家-淄博工贸有限公司 | 水质传感器_水质监测站_雨量监测站_水文监测站-山东水境传感科技有限公司 | 旋振筛|圆形摇摆筛|直线振动筛|滚筒筛|压榨机|河南天众机械设备有限公司 | ★济南领跃标识制作公司★济南标识制作,标牌制作,山东标识制作,济南标牌厂 | 水厂自动化-水厂控制系统-泵站自动化|控制系统-闸门自动化控制-济南华通中控科技有限公司 | 【同风运车官网】一站式汽车托运服务平台,验车满意再付款 | 石油/泥浆/不锈钢防腐/砂泵/抽砂泵/砂砾泵/吸砂泵/压滤机泵 - 专业石油环保专用泵厂家 | 森旺-A级防火板_石英纤维板_不燃抗菌板装饰板_医疗板 | 深圳高新投三江工业消防解决方案提供厂家_服务商_园区智慧消防_储能消防解决方案服务商_高新投三江 | 福建自考_福建自学考试网 | 原子吸收设备-国产分光光度计-光谱分光光度计-上海光谱仪器有限公司 |