国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

Java Shutdown Hook場(chǎng)景使用及源碼分析

瀏覽:4日期:2022-08-10 15:04:23
目錄背景Shutdown Hook 介紹關(guān)閉鉤子被調(diào)用場(chǎng)景注意事項(xiàng)實(shí)踐Shutdown Hook 在 Spring 中的運(yùn)用背景

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

Java 中的 Shutdown Hook 提供了比較好的方案。我們可以通過 Java.Runtime.addShutdownHook(Thread hook) 方法向 JVM 注冊(cè)關(guān)閉鉤子,在 JVM 退出之前會(huì)自動(dòng)調(diào)用執(zhí)行鉤子方法,做一些結(jié)尾操作,從而讓進(jìn)程平滑優(yōu)雅的退出,保證了業(yè)務(wù)的完整性。

Shutdown Hook 介紹

其實(shí),shutdown hook 就是一個(gè)簡(jiǎn)單的已初始化但是未啟動(dòng)的線程。當(dāng)虛擬機(jī)開始關(guān)閉時(shí),它將會(huì)調(diào)用所有已注冊(cè)的鉤子,這些鉤子執(zhí)行是并發(fā)的,執(zhí)行順序是不確定的。

在虛擬機(jī)關(guān)閉的過程中,還可以繼續(xù)注冊(cè)新的鉤子,或者撤銷已經(jīng)注冊(cè)過的鉤子。不過有可能會(huì)拋出 IllegalStateException。注冊(cè)和注銷鉤子的方法定義如下:

public void addShutdownHook(Thread hook) { // 省略}public void removeShutdownHook(Thread hook) { // 省略}關(guān)閉鉤子被調(diào)用場(chǎng)景

關(guān)閉鉤子可以在以下幾種場(chǎng)景被調(diào)用:

程序正常退出 程序調(diào)用 System.exit() 退出 終端使用 Ctrl+C 中斷程序 程序拋出異常導(dǎo)致程序退出,例如 OOM,數(shù)組越界等異常 系統(tǒng)事件,例如用戶注銷或關(guān)閉系統(tǒng) 使用 Kill pid 命令殺掉進(jìn)程,注意使用 kill -9 pid 強(qiáng)制殺掉不會(huì)觸發(fā)執(zhí)行鉤子

驗(yàn)證程序正常退出情況

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

運(yùn)行結(jié)果

程序開始啟動(dòng)...程序即將退出...執(zhí)行鉤子方法...

Process finished with exit code 0

驗(yàn)證程序調(diào)用 System.exit() 退出情況

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

運(yùn)行結(jié)果

程序開始啟動(dòng)...執(zhí)行鉤子方法...

Process finished with exit code -1

驗(yàn)證終端使用 Ctrl+C 中斷程序,在命令行窗口中運(yùn)行程序,然后使用 Ctrl+C 中斷

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

運(yùn)行結(jié)果

D:IdeaProjectsjava-demojava ShutdownHookDemo程序開始啟動(dòng)...執(zhí)行鉤子方法...

演示拋出異常導(dǎo)致程序異常退出

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

運(yùn)行結(jié)果

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

Process finished with exit code 1

至于系統(tǒng)被關(guān)閉,或者使用 Kill pid 命令殺掉進(jìn)程就不演示了,感興趣的可以自行驗(yàn)證。

注意事項(xiàng)

可以向虛擬機(jī)注冊(cè)多個(gè)關(guān)閉鉤子,但是注意這些鉤子執(zhí)行是并發(fā)的,執(zhí)行順序是不確定的。

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

運(yùn)行結(jié)果

程序開始啟動(dòng)...程序即將退出...執(zhí)行鉤子方法B...執(zhí)行鉤子方法C...執(zhí)行鉤子方法A...

向虛擬機(jī)注冊(cè)的鉤子方法需要盡快執(zhí)行結(jié)束,盡量不要執(zhí)行長(zhǎng)時(shí)間的操作,例如 I/O 等可能被阻塞的操作,死鎖等,這樣就會(huì)導(dǎo)致程序短時(shí)間不能被關(guān)閉,甚至一直關(guān)閉不了。我們也可以引入超時(shí)機(jī)制強(qiáng)制退出鉤子,讓程序正常結(jié)束。

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

以上的鉤子執(zhí)行時(shí)間比較長(zhǎng),最終會(huì)導(dǎo)致程序在等待很長(zhǎng)時(shí)間之后才能被關(guān)閉。

如果 JVM 已經(jīng)調(diào)用執(zhí)行關(guān)閉鉤子的過程中,不允許注冊(cè)新的鉤子和注銷已經(jīng)注冊(cè)的鉤子,否則會(huì)報(bào) IllegalStateException 異常。通過源碼分析,JVM 調(diào)用鉤子的時(shí)候,即調(diào)用 ApplicationShutdownHooks#runHooks() 方法,會(huì)將所有鉤子從變量 hooks 取出,然后將此變量置為 null。

// 調(diào)用執(zhí)行鉤子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) { } }}

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

// 注冊(cè)鉤子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('執(zhí)行鉤子方法...'); Runtime.getRuntime().addShutdownHook(new Thread( () -> System.out.println('在JVM調(diào)用鉤子的過程中再新注冊(cè)鉤子,會(huì)報(bào)錯(cuò)IllegalStateException'))); // 在JVM調(diào)用鉤子的過程中注銷鉤子,會(huì)報(bào)錯(cuò)IllegalStateException Runtime.getRuntime().removeShutdownHook(Thread.currentThread());})); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動(dòng)...');Thread.sleep(2000);System.out.println('程序即將退出...'); }}

運(yùn)行結(jié)果

程序開始啟動(dòng)...程序即將退出...執(zhí)行鉤子方法...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)

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

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

運(yùn)行結(jié)果

程序開始啟動(dòng)...程序即將退出...

Process finished with exit code 0

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

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

運(yùn)行結(jié)果

程序開始啟動(dòng)...程序即將退出...開始執(zhí)行鉤子方法...

Process finished with exit code -1

如果程序使用 Java Security Managers,使用 shutdown Hook 則需要安全權(quán)限 RuntimePermission(“shutdownHooks”),否則會(huì)導(dǎo)致 SecurityException。

實(shí)踐

例如,我們程序自定義了一個(gè)線程池,用來接收和處理任務(wù)。如果程序突然奔潰異常退出,這時(shí)線程池的所有任務(wù)有可能還未處理完成,如果不處理完程序就直接退出,可能會(huì)導(dǎo)致數(shù)據(jù)丟失,業(yè)務(wù)異常等重要問題。這時(shí)鉤子就派上用場(chǎng)了。

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('開始執(zhí)行鉤子方法...'); // 關(guān)閉線程池 executorService.shutdown(); try { // 等待60秒System.out.println(executorService.awaitTermination(60, TimeUnit.SECONDS)); } catch (InterruptedException e) {e.printStackTrace(); } System.out.println('結(jié)束執(zhí)行鉤子方法...');})); } public static void main(String[] args) throws InterruptedException {System.out.println('程序開始啟動(dòng)...');// 向線程池添加10個(gè)任務(wù)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...');} }}

在命令行窗口中運(yùn)行程序,在10個(gè)任務(wù)都提交到線程池之后,任務(wù)都還未處理完成之前,使用 Ctrl+C 中斷程序,最終在虛擬機(jī)關(guān)閉之前,調(diào)用了關(guān)閉鉤子,關(guān)閉線程池,并且等待60秒讓所有任務(wù)執(zhí)行完成。

Java Shutdown Hook場(chǎng)景使用及源碼分析

Shutdown Hook 在 Spring 中的運(yùn)用

Shutdown Hook 在 Spring 中是如何運(yùn)用的呢。通過源碼分析,Springboot 項(xiàng)目啟動(dòng)時(shí)會(huì)判斷 registerShutdownHook 的值是否為 true,默認(rèn)是 true,如果為真則向虛擬機(jī)注冊(cè)關(guān)閉鉤子。

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(); } } }; // 底層還是使用此方法注冊(cè)鉤子 Runtime.getRuntime().addShutdownHook(this.shutdownHook); }}

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

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 { // 發(fā)布Spring 應(yīng)用上下文的關(guān)閉事件,讓監(jiān)聽器在應(yīng)用關(guān)閉之前做出響應(yīng)處理 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 { // 執(zhí)行l(wèi)ifecycleProcessor的關(guān)閉方法 this.lifecycleProcessor.onClose(); } catch (Throwable ex) { logger.warn('Exception thrown from LifecycleProcessor on context close', ex); } } // 銷毀容器里所有單例Bean destroyBeans(); // 關(guān)閉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 并且實(shí)現(xiàn) DisposableBean 接口,重寫 destroy 對(duì)象銷毀方法。destroy 方法就是在 Spring 注冊(cè)的關(guān)閉鉤子里被調(diào)用的。例如我們使用 Spring 框架的 ThreadPoolTaskExecutor 線程池類,它就實(shí)現(xiàn)了 DisposableBean 接口,重寫了 destroy 方法,從而在程序退出前,進(jìn)行線程池銷毀工作。源碼如下:

@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); }}

到此這篇關(guān)于Java Shutdown Hook場(chǎng)景使用及源碼分析的文章就介紹到這了,更多相關(guān)Java Shutdown Hook內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 香港三级网站 | 动漫一级毛片 | 欧美成人精品第一区 | 欧美2区 | 亚洲影院在线 | 国产成人免费高清视频 | 国内视频自拍 | 免费高清国产 | aaaaaa级特色特黄的毛片 | 国产男人的天堂 | 免费一级欧美片片线观看 | 久草在线手机 | 亚洲成av人影片在线观看 | 亚洲国产欧洲精品路线久久 | 国产免费爽爽视频免费可以看 | 99久久精品国产综合一区 | 日韩一区二区三区在线免费观看 | 伊人久热这里只有精品视频99 | 色樱桃影院亚洲精品影院 | 日韩午夜在线 | 日本欧美国产精品 | 亚洲一区欧美一区 | 亚洲精品不卡视频 | 九九在线观看精品视频6 | a级毛片网站 | 国产精品视频一区二区猎奇 | 亚洲欧美在线播放 | 美女作爱网站 | 国产美女精品一区二区三区 | 亚洲精品一区91 | 手机在线毛片 | 欧美国产日韩一区二区三区 | 亚洲一区二区三区中文字幕 | 国产不卡精品一区二区三区 | 手机看片久久青草福利盒子 | 欧美一级毛片免费高清的 | 三级毛片在线 | 99久久精品久久久久久婷婷 | 久久亚洲国产精品 | 国产亚洲精品线观看77 | 精品午夜寂寞影院在线观看 |