前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >shutdownHook 死锁解决

shutdownHook 死锁解决

作者头像
潇洒
发布2023-10-23 14:12:40
1350
发布2023-10-23 14:12:40
举报
文章被收录于专栏:石头岛石头岛

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

复现问题

导致问题的代码,通过定位发现,程序在退出时卡住,线上代码敏感,写一个demo来复现:

代码语言:javascript
复制
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包装。

代码语言:javascript
复制
public void addShutdownHook(Thread hook) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("shutdownHooks"));
    }
    ApplicationShutdownHooks.add(hook);
}

重点:hooks,每个 hook线程put到hooks中。

代码语言:javascript
复制
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

代码语言:javascript
复制
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未释放的锁,所以卡住

代码语言:javascript
复制
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) {
            }
        }
    }
}

通过工具排查,可以清楚的看到,Thread-0 即shutdown线程去引用已经被main线程持有的锁对象,而导至被 BLOCKED 住

死锁
死锁

再看线程状态

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

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

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

解决

即然已经知道原因了,那就好办:

  1. 移除 shutdownHook 中不必要的加锁,shutdown 场景中很不需要用到加锁
  2. 使用不同的加锁对象,如果一定需要加锁,可以在 shutdownHook 的线程内使用一把新的锁,这样即可以保证安全性,又不会死锁。
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 复现问题
  • 原因
  • 解决
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com