日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

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

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

瀏覽:36日期: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
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
婷婷亚洲综合| 国产一卡不卡| 久久国产生活片100| 久久午夜精品| 美女少妇全过程你懂的久久| 欧美亚洲自偷自偷| 91精品在线观看国产| 日本成人中文字幕在线视频| 91精品一区国产高清在线gif| 国产一区二区三区国产精品| 久久精品超碰| 综合激情一区| 免费日韩视频| 亚洲一区二区成人| 在线视频精品| 老牛影视一区二区三区| 亚洲影院天堂中文av色| 日本中文字幕一区二区| 日韩高清中文字幕一区| 日韩国产一二三区| 国产精品伊人| 九九久久国产| 黄色在线网站噜噜噜| 久久国产电影| 在线看片不卡| 亚洲免费一区三区| 国产免费av国片精品草莓男男| 欧美日韩视频免费看| 久久av国产紧身裤| 成人高清一区| 亚洲制服少妇| 国产伦一区二区三区| 国产精品国产一区| 亚洲我射av| 欧美国产先锋| 日韩精品麻豆| 视频一区二区中文字幕| 国产精品网站在线看| 国产成人精品亚洲日本在线观看| 激情丁香综合| 日本强好片久久久久久aaa| 国产a亚洲精品| 欧美1级日本1级| 天堂精品久久久久| 国产一区二区三区不卡视频网站| 91视频一区| 一区二区精品| 欧美亚洲国产激情| 麻豆传媒一区二区三区| 成人福利av| 国产另类在线| 亚洲精品乱码久久久久久蜜桃麻豆 | 久久精品亚洲| 久久最新视频| 免费观看久久av| 水蜜桃久久夜色精品一区| 日韩一区二区三区免费视频| 天堂日韩电影| 国产精品99在线观看| 国产精品资源| 91成人在线| 日韩在线网址| 亚洲区欧美区| 免费国产自线拍一欧美视频| 青青久久av| 日韩高清成人| 日本韩国欧美超级黄在线观看| 国产精品国产三级在线观看| 亚洲精品大全| 日韩在线观看一区二区三区| 久久先锋影音| 蜜桃av一区二区三区电影| 模特精品在线| 蜜桃视频在线观看一区| 亚洲人成亚洲精品| 综合精品一区| 日韩欧美三区| 国产日韩免费| 国产精品xvideos88| 久久精品国产久精国产| 国产极品久久久久久久久波多结野 | 91成人在线网站| 欧美永久精品| 国产精品成人自拍| 一区二区三区四区日本视频| 久久高清免费| 在线观看一区| 国产精品久久久久77777丨| 国产日韩欧美一区二区三区| 精品伊人久久久| 999久久久精品国产| 男女精品网站| 欧美激情aⅴ一区二区三区| 黄色aa久久| 中文字幕av亚洲精品一部二部| 亚洲精品四区| 精品视频在线一区二区在线| 久久久国产精品一区二区中文| 欧美日韩国产传媒| 亚洲+小说+欧美+激情+另类| 精品一区二区三区视频在线播放| 欧洲精品一区二区三区| 蜜桃一区二区三区在线观看| 久久精品国产精品亚洲毛片| av亚洲在线观看| 国产精品久久久久久久免费软件| 国产99久久| 欧美精品成人| 欧美资源在线| 高清在线一区| 六月丁香综合| 国产在线|日韩| 麻豆久久久久久久| 中文字幕一区二区精品区| 国产suv精品一区| 国产欧美日韩精品一区二区三区 | 国产日韩电影| 国产欧美丝祙| 免费在线观看不卡| 99久久久久久中文字幕一区| 久久精品人人| 日本视频一区二区| 亚洲一区二区网站| 欧美sm一区| 狠狠久久伊人中文字幕| 久久黄色影视| 日韩视频1区| 亚洲精品激情| 中文字幕中文字幕精品| 亚洲欧美日韩在线观看a三区| 久久久一本精品| 午夜影院一区| 国产成人久久精品麻豆二区| 国产激情久久| 老鸭窝一区二区久久精品| 69堂精品视频在线播放| 日本国产欧美| 欧美日韩一区二区国产| 最新亚洲国产| 亚洲精品大片| 亚州av日韩av| 久久国产精品色av免费看| 国产精品扒开腿做爽爽爽软件| 日韩av中文字幕一区二区| 日本特黄久久久高潮| 国产免费播放一区二区| 麻豆久久久久久| 精品日韩视频| 亚洲资源av| 欧美精品三级在线| 国产专区精品| 欧洲激情综合| 日韩欧美中文字幕在线视频| 久久精品xxxxx| 国产精品成人a在线观看| 91精品亚洲| 日韩av资源网| 欧美日韩尤物久久| 蜜臀久久99精品久久一区二区| 亚洲一区亚洲| 麻豆视频久久| 尤物在线精品| 日韩激情中文字幕| 中文另类视频| 日本国产欧美| 亚洲a一区二区三区| 久久国产麻豆精品| av高清一区| 亚洲精品综合| 在线天堂中文资源最新版| 男人的天堂亚洲一区| 国产高潮在线| 久久精品99国产精品| 好看的av在线不卡观看| 美女性感视频久久| 日韩中文欧美在线| 91欧美在线| 国产欧美日本| 欧美资源在线| 亚洲电影在线| 国产精品不卡| 欧美国产亚洲精品| 一区二区国产精品| 在线观看精品| 国产精品久久久久久久久久久久久久久| 9色国产精品| 久久精品播放| 日韩一区三区| 麻豆高清免费国产一区| 亚洲男人在线| 蜜臀国产一区二区三区在线播放 | 日韩一区自拍| 你懂的国产精品永久在线| 日韩高清中文字幕一区| 蜜臀a∨国产成人精品| 91精品国产成人观看| 高潮一区二区| 成人亚洲精品| 国产成人久久精品一区二区三区| 国产精品久久久免费|