美文网首页
shutdownHook死锁问题解决

shutdownHook死锁问题解决

作者: 咪雅先森 | 来源:发表于2021-12-15 22:25 被阅读0次

    最近碰到一个问题,通过脚本执行kill -15后,程序并没有退出,进程一直都在,最后被退出脚本的通过kill -9,杀死。导致数据完整性被破坏,程序再重启后不可用。通过排查认后发现是在执行shutdownHook时死锁程序死锁。

    复现问题

    导致问题的代码,

    通过定位发现,程序在

    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 这个方法是怎么执行的,重点是 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(); 调用执行 hook的方法,之后调用 hook.join释放执行权。
    问题就出在 hook.join上,程序执行到这里之后,卡住死锁,出不去了。
    为什么,因为 join 实际就是 wait(0),一旦当前线程调用wait(0),就相当于释放执行权,等待其实线程notify()才能继续执行。
    但是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

    再看线程状态

    线程状态

    通过代码线程堆栈来确认就是这个原因

    1. main 方法是:WAIT 状态
    2. Thread-0是:RUNNING 状态,但是进入synchronized之后就会BLOCKED住

    这里就对应上图的两个线程的状态

    线程堆栈

    解决

    移除 shutdownHook 中不必要的加锁。

    1. 移除 shutdownHook 中不必要的加锁,shutdown 场景中很不需要用到加锁
    2. 使用不同的加锁对象,如果一定需要加锁,可以在 shutdownHook 的线程内使用一把新的锁,这样即可以保证安全性,又不会死锁。

    博客同步更新

    相关文章

      网友评论

          本文标题:shutdownHook死锁问题解决

          本文链接:https://www.haomeiwen.com/subject/bqcmfrtx.html