您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(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è)資訊頻道,感謝大家的支持。
免責(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)容。