溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

ShutdownHook如何讓?xiě)?yīng)用優(yōu)雅的停止

發(fā)布時(shí)間:2022-01-04 18:10:26 來(lái)源:億速云 閱讀:177 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)ShutdownHook如何讓?xiě)?yīng)用優(yōu)雅的停止,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

對(duì)于應(yīng)用,停止的時(shí)候一般都會(huì)把環(huán)境和數(shù)據(jù)恢復(fù)到運(yùn)行前的樣子,和單元測(cè)試時(shí)tearDown要做的效果基本類似。而為了實(shí)現(xiàn)停止時(shí)恢復(fù)現(xiàn)場(chǎng),英文中有個(gè)特定的描述:

shutdown gracefully

在Java中,為了實(shí)現(xiàn)gracefully shutdown,有一個(gè)特定的接口可供使用,就是我們今天要提到的shutdown hook。它與回調(diào)函數(shù)功能類似,文檔中對(duì)其描述如下:

關(guān)閉鉤子 只是一個(gè)已初始化但尚未啟動(dòng)的線程。虛擬機(jī)開(kāi)始啟用其關(guān)閉序列時(shí),它會(huì)以某種未指定的順序啟動(dòng)所有已注冊(cè)的關(guān)閉鉤子,并讓它們同時(shí)運(yùn)行。運(yùn)行完所有的鉤子后,如果已啟用退出終結(jié),那么虛擬機(jī)接著會(huì)運(yùn)行所有未調(diào)用的終結(jié)方法。最后,虛擬機(jī)會(huì)暫停。注意,關(guān)閉序列期間會(huì)繼續(xù)運(yùn)行守護(hù)線程,如果通過(guò)調(diào)用 exit 方法來(lái)發(fā)起關(guān)閉序列,那么也會(huì)繼續(xù)運(yùn)行非守護(hù)線程。

要為應(yīng)用添加shutdownHook,需要做的只是這樣的下操作:

Runtime.getRuntime().addShutdownHook(new Thread() {
 
    public void run() { /*
       my shutdown code here
    */ }
 });

向addShutdownHook方法傳入的Thread,其run方法即為自定義的shutdown時(shí)清理邏輯。

JDK內(nèi)部,是通過(guò)一個(gè)Map來(lái)保存所有添加的ShutdownHook,在被觸發(fā)時(shí)執(zhí)行。

public void addShutdownHook(Thread hook) {
   SecurityManager sm = System.getSecurityManager();
   if (sm != null) {
       sm.checkPermission(new RuntimePermission("shutdownHooks"));
   }
   ApplicationShutdownHooks.add(hook);
 

 
static synchronized void add(Thread hook) {
    hooks.put(hook, hook);
}
 

 
private static IdentityHashMap<Thread, Thread> hooks;
 
hooks = new IdentityHashMap<>();

那這個(gè)shutdownHook一般是在什么時(shí)候會(huì)被調(diào)用呢?

我們看JDK的文檔中描述,對(duì)于兩類事件Java虛擬機(jī)會(huì)退出:

Java 虛擬機(jī)會(huì)為了響應(yīng)以下兩類事件而關(guān)閉

  • 程序正常退出,這發(fā)生在最后的非守護(hù)線程退出時(shí),或者在調(diào)用 exit(等同于 System.exit)方法時(shí)。或者,

  • 為響應(yīng)用戶中斷而終止 虛擬機(jī),如鍵入 ^C;或發(fā)生系統(tǒng)事件,比如用戶注銷或系統(tǒng)關(guān)閉。 

在Java虛擬機(jī)退出的時(shí)候,這些設(shè)置的shutdownHook會(huì)被并行的調(diào)用。但需要注意的是,對(duì)于非正常方式退出Java虛擬機(jī),例如殺進(jìn)程,系統(tǒng)斷電等,這些情況下,shutdownHook不會(huì)被執(zhí)行。

我們來(lái)看,在Tomcat內(nèi)部,是如何使用ShutdownHook的。

Catalina類內(nèi)部,在服務(wù)器內(nèi)部各個(gè)組件啟動(dòng)完畢后,有這樣一段代碼


 
// Register shutdown hook
if (useShutdownHook) {
   if (shutdownHook == null) {
       shutdownHook = new CatalinaShutdownHook();
   }
   Runtime.getRuntime().addShutdownHook(shutdownHook);
 
}

和我們?cè)诒疚拈_(kāi)頭看到的一樣,他注冊(cè)了一個(gè)ShutdownHook。這個(gè)hook的內(nèi)容如下:


 
/**
* Shutdown hook which will perform a clean shutdown of Catalina if needed.
*/
protected class CatalinaShutdownHook extends Thread {
   public void run() {
       try {
           if (getServer() != null) {
               Catalina.this.stop();
           }
       } catch (Throwable ex) {
       } finally {
       }
   }
}

看就是在shutdown的時(shí)候通過(guò)調(diào)用應(yīng)用服務(wù)器的stop方法,來(lái)shutdown gracefully。

而我們一般停止Tomcat會(huì)通過(guò)調(diào)用shutdown腳本的方式進(jìn)行,這個(gè)時(shí)候,腳本實(shí)質(zhì)上去執(zhí)行了應(yīng)用服務(wù)器的stop方法來(lái)進(jìn)行停止,而不是被shutdownHook來(lái)反調(diào)過(guò)來(lái)的。

因此,在讀源碼時(shí),你會(huì)發(fā)現(xiàn)代碼中有這樣的內(nèi)容:

/**
* Stop an existing server instance.
*/
public void stop() {

   try {
       // Remove the ShutdownHook first so that server.stop()
       // doesn't get invoked twice
       if (useShutdownHook) {
        Runtime.getRuntime().removeShutdownHook(shutdownHook);  
   } catch (Throwable t) {
       ExceptionUtils.handleThrowable(t);}

我們看到,在停止Server的時(shí)候,會(huì)先執(zhí)行removeShutdownHook的操作,注釋里寫(xiě)的明白,是為了防止server和stop被執(zhí)行兩次。

哪種情況下會(huì)被執(zhí)行兩次呢?

當(dāng)使用catalina的shutdown腳本停止Server時(shí),這個(gè)方法并不是從shutdownHook調(diào)用過(guò)來(lái),此時(shí),shutdownHook還沒(méi)有執(zhí)行,所以在JVM退出的時(shí)候,shutdownHook會(huì)被觸發(fā)。如果此處還沒(méi)去remove掉的話,就還會(huì)調(diào)用過(guò)來(lái)。這一點(diǎn)在我們自己開(kāi)發(fā)應(yīng)用時(shí)需要注意借鑒一下。

看到這里,希望你不要說(shuō)然并卵。

當(dāng)然,如果你已經(jīng)脫口而出,那...

我找了例子證明,你已經(jīng)在不知不覺(jué)中使用了shutdownHook,例如你在輸出日志的時(shí)候,以下代碼是JDK的LogManager中的一部分,我們看到,其構(gòu)造方法中直接會(huì)添加一個(gè)名為Cleaner的shutdownHook。

private LogManager(Void checked) {
   // Add a shutdown hook to close the global handlers.
   try {
       Runtime.getRuntime().addShutdownHook(new Cleaner());
   } catch (IllegalStateException e) {
       // If the VM is already shutting down,
       // We do not need to register shutdownHook.
   }
}
 

public void run() {

// This is to ensure the LogManager.<clinit> is completed
   // before synchronized block. Otherwise deadlocks are possible.
   LogManager mgr = manager;
   // Do a reset to close all active handlers.
   reset();
}

我們自己添加shutdownHook的時(shí)候,需要注意的是:

在內(nèi)部不要寫(xiě)耗時(shí)的操作,也不要寫(xiě)容易引起死鎖的操作。多個(gè)ShutdownHook之間如果存在資源競(jìng)爭(zhēng)而死鎖,那應(yīng)用就停止不了了。

看完上述內(nèi)容,你們對(duì)ShutdownHook如何讓?xiě)?yīng)用優(yōu)雅的停止有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI