最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2021年06月15日 09:32:37   作者:陳皮的JavaLib  
shutdown hook 就是一個簡單的已初始化但是未啟動的線程,本文詳細的介紹了Java Shutdown Hook場景使用及源碼分析,感興趣的朋友可以參考一下

背景

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

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

Shutdown Hook 介紹

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

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

public void addShutdownHook(Thread hook) {
 // 省略
}

public void removeShutdownHook(Thread hook) {
 // 省略
}

關(guān)閉鉤子被調(diào)用場景

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

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

驗證程序正常退出情況

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("程序開始啟動...");
        Thread.sleep(2000);
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

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

Process finished with exit code 0

驗證程序調(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("程序開始啟動...");
        Thread.sleep(2000);
        System.exit(-1);
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

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

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("執(zhí)行鉤子方法...")));
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("程序開始啟動...");
        Thread.sleep(2000);
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

D:\IdeaProjects\java-demo\java ShutdownHookDemo
程序開始啟動...
執(zhí)行鉤子方法...

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

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("程序開始啟動...");
        int a = 0;
        System.out.println(10 / a);
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

程序開始啟動...
執(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 命令殺掉進程就不演示了,感興趣的可以自行驗證。

注意事項

可以向虛擬機注冊多個關(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("程序開始啟動...");
        Thread.sleep(2000);
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

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

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

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("程序即將退出...");
    }
}

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

如果 JVM 已經(jīng)調(diào)用執(zhí)行關(guān)閉鉤子的過程中,不允許注冊新的鉤子和注銷已經(jīng)注冊的鉤子,否則會報 IllegalStateException 異常。通過源碼分析,JVM 調(diào)用鉤子的時候,即調(diào)用 ApplicationShutdownHooks#runHooks() 方法,會將所有鉤子從變量 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) { }
    }
}

在注冊和注銷鉤子的方法中,首先會判斷 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("執(zhí)行鉤子方法...");
            Runtime.getRuntime().addShutdownHook(new Thread(
                    () -> System.out.println("在JVM調(diào)用鉤子的過程中再新注冊鉤子,會報錯IllegalStateException")));
            // 在JVM調(diào)用鉤子的過程中注銷鉤子,會報錯IllegalStateException
            Runtime.getRuntime().removeShutdownHook(Thread.currentThread());
        }));
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("程序開始啟動...");
        Thread.sleep(2000);
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

程序開始啟動...
程序即將退出...
執(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,那么虛擬機是不會調(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("程序開始啟動...");
        System.out.println("程序即將退出...");
        Runtime.getRuntime().halt(0);
    }
}

運行結(jié)果

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

Process finished with exit code 0

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

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("程序開始啟動...");
        System.out.println("程序即將退出...");
    }
}

運行結(jié)果

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

Process finished with exit code -1

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

實踐

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

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("程序開始啟動...");
        // 向線程池添加10個任務(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...");
        }
    }
}

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

Shutdown Hook 在 Spring 中的運用

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

private void refreshContext(ConfigurableApplicationContext context) {
 refresh(context);
 if (this.registerShutdownHook) {
  try {
   context.registerShutdownHook();
  }
  catch (AccessControlException ex) {
   // Not allowed in some environments.
  }
 }
}

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

在關(guān)閉鉤子的方法 doClose 中,會做一些虛擬機關(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 并且實現(xiàn) DisposableBean 接口,重寫 destroy 對象銷毀方法。destroy 方法就是在 Spring 注冊的關(guān)閉鉤子里被調(diào)用的。例如我們使用 Spring 框架的 ThreadPoolTaskExecutor 線程池類,它就實現(xiàn)了 DisposableBean 接口,重寫了 destroy 方法,從而在程序退出前,進行線程池銷毀工作。源碼如下:

@Override
public 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場景使用及源碼分析的文章就介紹到這了,更多相關(guān)Java Shutdown Hook內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決idea每次新建項目都需要重新指定maven目錄

    解決idea每次新建項目都需要重新指定maven目錄

    這篇文章主要介紹了解決idea每次新建項目都需要配置maven,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • SpringMVC中Model和ModelAndView的EL表達式取值方法

    SpringMVC中Model和ModelAndView的EL表達式取值方法

    下面小編就為大家分享一篇SpringMVC中Model和ModelAndView的EL表達式取值方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • 如何使用IDEA 搭建 SpringCloud 項目

    如何使用IDEA 搭建 SpringCloud 項目

    所謂微服務(wù),就是要把整個業(yè)務(wù)模塊拆分成多個各司其職的小模塊,做到單一職責原則,不會重復(fù)開發(fā)相同的業(yè)務(wù)代碼,實現(xiàn)真正意義上的高內(nèi)聚、低耦合,這篇文章主要介紹了如何使用IDEA 搭建 SpringCloud 項目,需要的朋友可以參考下
    2023-11-11
  • 在Java的MyBatis框架中建立接口進行CRUD操作的方法

    在Java的MyBatis框架中建立接口進行CRUD操作的方法

    這篇文章主要介紹了在Java的MyBatis框架中建立接口進行CRUD操作的方法,CRUD是指在做計算處理時的增加(Create)、重新取得數(shù)據(jù)(Retrieve)、更新(Update)和刪除(Delete)幾個單詞的首字母簡寫,需要的朋友可以參考下
    2016-04-04
  • MyBatis中正則使用foreach拼接字符串

    MyBatis中正則使用foreach拼接字符串

    這篇文章主要介紹了MyBatis中正則使用foreach拼接字符串,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java方法能定義多少個參數(shù)你知道嗎

    Java方法能定義多少個參數(shù)你知道嗎

    這篇文章主要給大家介紹了關(guān)于Java方法能定義多少個參數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用java具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-09-09
  • Java中打亂一個數(shù)組的2種公平算法分享

    Java中打亂一個數(shù)組的2種公平算法分享

    這篇文章主要介紹了Java中打亂一個數(shù)組的2種公平算法分享,本文講解了洗牌程序原理、生成隨機索引交換二種方法并給出示例代碼,需要的朋友可以參考下
    2015-03-03
  • Java設(shè)計模式之java裝飾者模式詳解

    Java設(shè)計模式之java裝飾者模式詳解

    這篇文章主要為大家詳細介紹了java設(shè)計模式之裝飾者模式,裝飾者模式是一種結(jié)構(gòu)式模式,感興趣的朋友可以參考一下,希望能夠給你帶來幫助
    2021-09-09
  • Spring?MVC的三種異常處理方式實例詳解

    Spring?MVC的三種異常處理方式實例詳解

    在SpringMVC 中,不管是編譯異常還是運行時異常,都可以最終由 SpringMVC提供的異常處理器進行統(tǒng)一處理,這樣就避免了隨時隨地捕獲處理的繁瑣性,這篇文章主要介紹了Spring?MVC的三種異常處理方式?,需要的朋友可以參考下
    2024-01-01
  • Java實現(xiàn)Dijkstra輸出最短路徑的實例

    Java實現(xiàn)Dijkstra輸出最短路徑的實例

    這篇文章主要介紹了Java實現(xiàn)Dijkstra輸出最短路徑的實例的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-09-09

最新評論

中西区| 菏泽市| 遂平县| 日喀则市| 阿拉善右旗| 互助| 申扎县| 华池县| 额尔古纳市| 新蔡县| 昭通市| 蒙阴县| 固原市| 濮阳市| 旺苍县| 武山县| 东光县| 长丰县| 独山县| 贵州省| 峨山| 丰宁| 彰化县| 柘荣县| 安新县| 呼伦贝尔市| 怀宁县| 象山县| 尤溪县| 泗洪县| 临城县| 岳西县| 息烽县| 依安县| 广南县| 毕节市| 沿河| 腾冲县| 栖霞市| 扶余县| 平罗县|