最近碰到一個問題,通過腳本執(zhí)行kill -15后,程序并沒有退出,進(jìn)程一直都在,最后被退出腳本的通過kill -9,殺死。導(dǎo)致數(shù)據(jù)完整性被破壞,程序再重啟后不可用。通過排查認(rèn)后發(fā)現(xiàn)是在執(zhí)行shutdownHook時死鎖程序死鎖。
復(fù)現(xiàn)問題
導(dǎo)致問題的代碼,
通過定位發(fā)現(xiàn),程序在
public class Test {
private static final Object lock = new Object();
public static void main(String... args) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Locking");
synchronized (lock) {
System.out.println("Locked");
}
}
}));
synchronized (lock) {
System.out.println("Exiting");
System.exit(0);
}
}
}
輸出:
Exiting
Locking
原因
排查原因
分析一下 addShutdownHook 這個方法是怎么執(zhí)行的,重點是 ApplicationShutdownHooks,每一個 shutdownHook 都使用一個Thread包裝。
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
重點:hooks,每個 hook線程put到hooks中。
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);
}
添加后誰來處理shutdown這個操作,是 Shutdown.add 這里起了一個線程,處理所以主要的邏輯在 runHooks
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}
這段代碼中 hook.start(); 調(diào)用執(zhí)行 hook的方法,之后調(diào)用 hook.join釋放執(zhí)行權(quán)。
問題就出在 hook.join上,程序執(zhí)行到這里之后,卡住死鎖,出不去了。
為什么,因為 join 實際就是 wait(0),一旦當(dāng)前線程調(diào)用wait(0),就相當(dāng)于釋放執(zhí)行權(quán),等待其實線程notify()才能繼續(xù)執(zhí)行。
但是main線程調(diào)用System.exit(0)后,synchronized 當(dāng)前線程為 main,hook.join拿不到被main未釋放的鎖,所以卡住
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
通過工具排查

再看線程狀態(tài)
通過代碼線程堆棧來確認(rèn)就是這個原因
- main 方法是:WAIT 狀態(tài)
- Thread-0是:RUNNING 狀態(tài),但是進(jìn)入synchronized之后就會BLOCKED住
這里就對應(yīng)上圖的兩個線程的狀態(tài)
解決
移除 shutdownHook 中不必要的加鎖。
- 移除 shutdownHook 中不必要的加鎖,shutdown 場景中很不需要用到加鎖
- 使用不同的加鎖對象,如果一定需要加鎖,可以在 shutdownHook 的線程內(nèi)使用一把新的鎖,這樣即可以保證安全性,又不會死鎖。

