shutdownHook死鎖問題解決

最近碰到一個問題,通過腳本執(zhí)行kill -15后,程序并沒有退出,進程一直都在,最后被退出腳本的通過kill -9,殺死。導致數(shù)據(jù)完整性被破壞,程序再重啟后不可用。通過排查認后發(fā)現(xiàn)是在執(zhí)行shutdownHook時死鎖程序死鎖。

復現(xiàn)問題

導致問題的代碼,

通過定位發(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(); 調用執(zhí)行 hook的方法,之后調用 hook.join釋放執(zhí)行權。
問題就出在 hook.join上,程序執(zhí)行到這里之后,卡住死鎖,出不去了。
為什么,因為 join 實際就是 wait(0),一旦當前線程調用wait(0),就相當于釋放執(zhí)行權,等待其實線程notify()才能繼續(xù)執(zhí)行。
但是main線程調用System.exit(0)后,synchronized 當前線程為 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)

通過代碼線程堆棧來確認就是這個原因

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

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

解決

移除 shutdownHook 中不必要的加鎖。

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

博客同步更新

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

友情鏈接更多精彩內容