shutdownHook死鎖問題解決

最近碰到一個問題,通過腳本執(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) {
                }
            }
        }
    }

通過工具排查

死鎖.png

再看線程狀態(tài)

通過代碼線程堆棧來確認(rèn)就是這個原因

  1. main 方法是:WAIT 狀態(tài)
  2. Thread-0是:RUNNING 狀態(tài),但是進(jìn)入synchronized之后就會BLOCKED住

這里就對應(yīng)上圖的兩個線程的狀態(tài)

解決

移除 shutdownHook 中不必要的加鎖。

  1. 移除 shutdownHook 中不必要的加鎖,shutdown 場景中很不需要用到加鎖
  2. 使用不同的加鎖對象,如果一定需要加鎖,可以在 shutdownHook 的線程內(nèi)使用一把新的鎖,這樣即可以保證安全性,又不會死鎖。

博客同步更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容