Java提供了Shutdown Hook機制,它讓咱們在程序正常退出或者發生異常時能有機會作一些清場工做。使用的方法也很簡單,Java.Runtime.addShutdownHook(Thread hook)
便可。關閉鉤子其實能夠當作是一個已經初始化了的但還沒啓動的線程,當JVM關閉時會併發地執行註冊的全部關閉鉤子。html
向JVM註冊關閉鉤子後的何時會被調用,何時不會被調用呢?分紅如下狀況:java
鉤子的添加和刪除都是經過 Runtime 來實現,裏面的實現也比較簡單,能夠看到 addShutdownHook 和 removeShutdownHook 方法都是先經過安全管理器先檢查是否有 shutdownHooks 的權限,而後再經過 ApplicationShutdownHooks 添加和刪除鉤子。linux
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
public boolean removeShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
return ApplicationShutdownHooks.remove(hook);
}複製代碼
ApplicationShutdownHooks 能夠當作是用來保管全部關閉鉤子的容器,而主要是經過一個 IdentityHashMap 類型的變量來保存鉤子。windows
private static IdentityHashMap<Thread, Thread> hooks;複製代碼
有了 hooks 這個變量,添加刪除鉤子就是直接向這個 HashMap 進行 put 和 remove 操做了,其中在操做前也會作一些檢查,好比添加鉤子前會作三個判斷:數組
相似的判斷邏輯還有 remove 操做。安全
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;
}複製代碼
而 ApplicationShutdownHooks 中真正負責啓動全部鉤子的任務由 runHooks 方法負責,它的邏輯以下:bash
用 join 方法協調全部鉤子線程,等待他們執行完畢。併發
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) { }
}
}複製代碼
ApplicationShutdownHooks 的 runHooks 方法又是由誰負責調用的呢?以下,它實際上是變成一個 Runnable 對象添加到 Shutdown 類中了,Runnable 的 run 方法負責調用 runHooks 方法。接下去就要看 Shutdown 類何時執行該 Runnable 對象了。優化
Shutdown.add(1 , false ,
new Runnable() {
public void run() {
runHooks();
}
}
);複製代碼
ApplicationShutdownHooks 的 Runnable 對象添加到 Shutdown 中的邏輯以下,ui
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {
if (state > RUNNING)
throw new IllegalStateException("Shutdown in progress");
} else {
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}複製代碼
slot表示將Runnable對象賦給 hooks 數組中的哪一個元素中, Shutdown 中一樣有一個 hooks 變量,它是 Runnable[] 類型,長度爲 MAX_SYSTEM_HOOKS ,即爲 10 。這個數組能夠當作是鉤子的優先級實現,數組下標用於表示優先級,slot = 1 則表示賦值到數組中第二個元素。
registerShutdownInProgress 表示是否容許註冊鉤子,即便正在執行 shutdown 。前面傳入 false ,顯然是不容許。其中 state > RUNNING 條件表示其餘狀態都要拋出異常,除非是 RUNNING 狀態,這個很好理解,一共有三個狀態,RUNNING、HOOKS、FINALIZERS,值分別爲0、一、2。若是 registerShutdownInProgress 爲 true 則只要不爲 FINALIZERS 狀態,同時 slot 也要大於當前鉤子數組的下標便可。
在前面說到的鉤子執行時機的狀況下,JVM都會調用到 Shutdown 類的 sequence 方法,以下,
private static void sequence() {
synchronized (lock) {
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}複製代碼
首先判斷當前狀態不等於 HOOKS 則直接返回,接着執行 runHooks 方法,這個方法也是咱們主要要看的方法。而後再將狀態設爲 FINALIZERS ,最後若是須要的話還要調用 runAllFinalizers 方法執行全部 finalizer。因此在JVM關閉時 runHooks 方法是會被調用的。
runHooks 方法邏輯簡單,就是遍歷 Runnable 數組,一個個調用其 run 方法讓其執行。
如下是廣告和相關閱讀
========廣告時間========
鄙人的新書《Tomcat內核設計剖析》已經在京東銷售了,有須要的朋友能夠到 item.jd.com/12185360.ht… 進行預約。感謝各位朋友。
=========================
相關閱讀:
從JDK源碼角度看Object
從JDK源碼角度看Long
從JDK源碼角度看Integer
從JDK源碼角度看Float
volatile足以保證數據同步嗎
談談Java基礎數據類型
從JDK源碼角度看併發鎖的優化
從JDK源碼角度看線程的阻塞和喚醒
從JDK源碼角度看併發競爭的超時
從JDK源碼角度看java併發線程的中斷
從JDK源碼角度看Java併發的公平性
從JDK源碼角度看java併發的原子性如何保證
從JDK源碼角度看Byte
從JDK源碼角度看Boolean
從JDK源碼角度看Short
從JDK源碼看System.exit
歡迎關注: