一个有效的收拾程序运行残局的方法--ShutdownHook

付威     2019-11-10   5439   15min  

发现addShutdownHook

在阅读QMQ的源码的时候,在Server端启动的时候,注册了一个shutdown的代码,具体的代码如下:

 Runtime.getRuntime().addShutdownHook(new Thread(wrapper::destroy));

addShutdownHook作用

addShutdownHook方法可以添加一个指定的线程来在Java程序退出的时候做一些事情,在以下几个场景会被调用:

  1. 程序运行完成后退出
  2. 使用Ctrl+C时终端退出
  3. 调用系统退出的方法, System.exit(0)

具体的使用Demo如下:

  public class AppMain {
    public static void main(String[] args) {
      Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit01));
      Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit02));
      Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit03));
      while (true){
        System.out.println(".....");
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
  
    }
    public static void exit01(){
      System.out.println("exit01");
    }
    public static void exit02(){
      System.out.println("exit02");
    }
    public static void exit03(){
      System.out.println("exit03");
    }
  }

执行当执行退出的时候: 程序退出

addShutdownHook的使用场景

addShutdownHook的使用场景很多, 尤其是在通信的模块功能,比如Netty为例,当服务端或者客户端异常断开的时候,需要告诉对方释放资源,从而保证不会触发异常机制。

例如分布式队列,RPC框架都需要在异常后,释放相关资源。

addShutdownHook使用注意几点

  1. 在多次使用addShutdownHook增加退出的方法是,由于是开一个线程,所以不能保证方法的执行是按顺序的。 所以尽量保证方法在一个方法里面完成。

  2. 在释放的时候不应该增加阻塞的方法,否者会导致推出时阻塞,导致无法退出

       Runtime.getRuntime().addShutdownHook(new Thread(() -> {
           while (true) {
             //
           }
         }));
    

hooks相关源码

 public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }
  
     public boolean removeShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        return ApplicationShutdownHooks.remove(hook);
    }


class ApplicationShutdownHooks {
    /* The set of registered hooks */
    private static IdentityHashMap<Thread, Thread> hooks;
    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;
        }
    }


    private ApplicationShutdownHooks() {}

    /* Add a new shutdown hook.  Checks the shutdown state and the hook itself,
     * but does not do any security checks.
     */
    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);
    }

    /* Remove a previously-registered hook.  Like the add method, this method
     * does not do any security checks.
     */
    static synchronized boolean remove(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook == null)
            throw new NullPointerException();

        return hooks.remove(hook) != null;
    }

    /* Iterates over all application hooks creating a new thread for each
     * to run in. Hooks are run concurrently and this method waits for
     * them to finish.
     */
    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) {
                }
            }
        }
    }
}


Hooks管理问题

在很多开源的框架中,有的Hooks反而会起到反作用,我们怎么样能够移除我们不需要的Hooks呢?

在看了上面的代码,我们发现ApplicationShutdownHooks 不是一个public的类,JDK也没有提供对外管理的方法,只有addremove方法。

通过分析ApplicationShutdownHooks类源码的结构,我们可以发现,用来存放hooks的文件的是一个IdentityHashMap容器,而且是一个静态的属性。

从反射入手

既然是一个变量,可以通过反射来获得对应的Hooks的值,代码如下:

      String className = "java.lang.ApplicationShutdownHooks";
      Class<?> clazz = Class.forName(className);
      Field field = clazz.getDeclaredField("hooks");
      field.setAccessible(true);
      IdentityHashMap<Thread, Thread> map = (IdentityHashMap<Thread, Thread>) field.get(clazz);
      for (Thread thread : map.keySet()) {
          RzLogger.info("found shutdownHook: " + thread.getName());
      }

增加三个Hook验证:


  Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit01));
  Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit02));
  Runtime.getRuntime().addShutdownHook(new Thread(AppMain::exit03));



    23:33:11,102  INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-2
    23:33:11,106  INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-0
    23:33:11,106  INFO [main] (RzLogger.java:12) - found shutdownHook: Thread-1
    exit03
    exit01
    exit02   

收获

  1. ShutDown的使用,在程序退出后清理现场
  2. 一步一步去理解代码的原理和特性,细节很重要
(本文完)

作者:付威

博客地址:http://blog.laofu.online

如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作

如有任何知识产权、版权问题或理论错误,还请指正。

本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证

交流请加群113249828: 点击加群   或发我邮件 laofu_online@163.com

付威

获得最新的博主文章,请关注上方公众号