• Java关闭钩子


    背景

    在JVM退出时,我们有时候希望系统帮忙完成一些清场工作,例如状态同步,系统资源释放等等。JAVA中的ShutdownHook提供了比较好的方案。

    什么时shutdownHook?

    Shutdown hook是一个initialized but unstarted thread。当JVM开始执行shutdown sequence时,会并发运行所有registered Shutdown Hook。这时,在Shutdown Hook这个线程里定义的操作便会开始执行。需要注意的是,在Shutdown Hook里执行的操作应当是不太耗时的。因为在用户注销或者操作系统关机导致的JVM shutdown的例子中,系统只会预留有限的时间给未完成的工作,超时之后还是会强制关闭。


    JDK提供了Java.Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在一下几种场景中被调用:

    1. 程序正常退出
    2. 使用System.exit()
    3. 终端使用Ctrl+C触发的中断
    4. 系统关闭
    5. OutOfMemory宕机
    6. 使用Kill pid命令干掉进程(注:在使用kill -9 pid时,是不会被调用的

    如何使用Shutdown Hook

    调用java.lang.Runtime这个类的addShutdownHook(Thread hook)方法即可注册一个Shutdown Hook,然后在Thread中定义需要在system exit时进行的操作。如下:

    Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Do something in Shutdown Hook")));
    实例代码如下:
     1 package com.demo;
     2 
     3 public class ShutdownHookTest {
     4 
     5     public static void main(String[] args) {
     6         Thread hook = new Thread(new Hook("A"));
     7         Runtime.getRuntime().addShutdownHook(hook);
     8         hook = new Thread(new Hook("B"));
     9         Runtime.getRuntime().addShutdownHook(hook);
    10         hook = new Thread(new Hook("C"));
    11         Runtime.getRuntime().addShutdownHook(hook);
    12         hook = new Thread(new Hook("D"));
    13         Runtime.getRuntime().addShutdownHook(hook);
    14         int count = 0;
    15         while (count < 10) {
    16             new Thread(new NomalThread()).start();
    17             count++;
    18         }
    19     }
    20 }
    21 
    22 class Hook implements Runnable {
    23 
    24     private String hookName;
    25 
    26     public Hook(String hookName) {
    27         super();
    28         this.hookName = hookName;
    29     }
    30 
    31     @Override
    32     public void run() {
    33         System.out.println("i am the shutdown hook[" + hookName + "]");
    34     }
    35 
    36 }
    37 
    38 class NomalThread implements Runnable {
    39 
    40     @Override
    41     public void run() {
    42         System.out.println(String.format("Thread %s running", Thread.currentThread().getName()));
    43         try {
    44             Thread.sleep(1000);
    45         } catch (InterruptedException e) {
    46             e.printStackTrace();
    47         }
    48     }
    49 
    50 }
    View Code

    源码解读

      1 //java.lang.Runtime
      2 public void addShutdownHook(Thread hook) {
      3         SecurityManager sm = System.getSecurityManager();
      4         if (sm != null) {
      5             sm.checkPermission(new RuntimePermission("shutdownHooks"));
      6         }
      7         ApplicationShutdownHooks.add(hook);
      8     }
      9 
     10 //java.lang.ApplicationShutdownHooks
     11 /* Add a new shutdown hook.  Checks the shutdown state and the hook itself,
     12      * but does not do any security checks.
     13      */
     14     static synchronized void add(Thread hook) {
     15         if(hooks == null)
     16             throw new IllegalStateException("Shutdown in progress");
     17 
     18         if (hook.isAlive())
     19             throw new IllegalArgumentException("Hook already running");
     20 
     21         if (hooks.containsKey(hook))
     22             throw new IllegalArgumentException("Hook previously registered");
     23 
     24         hooks.put(hook, hook);
     25     }
     26 
     27 //hooks有什么用?
     28 /* Iterates over all application hooks creating a new thread for each
     29      * to run in. Hooks are run concurrently and this method waits for
     30      * them to finish.
     31      */
     32     static void runHooks() {
     33         Collection<Thread> threads;
     34         synchronized(ApplicationShutdownHooks.class) {
     35             threads = hooks.keySet();
     36             hooks = null;
     37         }
     38 
     39         for (Thread hook : threads) {
     40             hook.start();
     41         }
     42         for (Thread hook : threads) {
     43             while (true) {
     44                 try {
     45                     hook.join();
     46                     break;
     47                 } catch (InterruptedException ignored) {
     48                 }
     49             }
     50         }
     51     }
     52 
     53 
     54 /*
     55 这些钩子会在ApplicationShutdownHooks的初始化的时候,在static块里面被添加到Shudown的hooks里面
     56 java.lang.ApplicationShutdownHooks
     57 */
     58 static {
     59         try {
     60             Shutdown.add(1 /* shutdown hook invocation order */,
     61                 false /* not registered if shutdown in progress */,
     62                 new Runnable() {
     63                     public void run() {
     64                         runHooks();
     65                     }
     66                 }
     67             );
     68             hooks = new IdentityHashMap<>();
     69         } catch (IllegalStateException e) {
     70             // application shutdown hooks cannot be added if
     71             // shutdown is in progress.
     72             hooks = null;
     73         }
     74     }
     75 // 注意,Shutdown的hooks和ApplicationShutdownHooks.hooks不同
     76 //添加到Shutdown的hooks
     77 /**
     78      * Add a new system shutdown hook.  Checks the shutdown state and
     79      * the hook itself, but does not do any security checks.
     80      *
     81      * The registerShutdownInProgress parameter should be false except
     82      * registering the DeleteOnExitHook since the first file may
     83      * be added to the delete on exit list by the application shutdown
     84      * hooks.
     85      *
     86      * @params slot  the slot in the shutdown hook array, whose element
     87      *               will be invoked in order during shutdown
     88      * @params registerShutdownInProgress true to allow the hook
     89      *               to be registered even if the shutdown is in progress.
     90      * @params hook  the hook to be registered
     91      *
     92      * @throws IllegalStateException
     93      *         if registerShutdownInProgress is false and shutdown is in progress; or
     94      *         if registerShutdownInProgress is true and the shutdown process
     95      *         already passes the given slot
     96      */
     97     static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
     98         if (slot < 0 || slot >= MAX_SYSTEM_HOOKS) {
     99             throw new IllegalArgumentException("Invalid slot: " + slot);
    100         }
    101         synchronized (lock) {
    102             if (hooks[slot] != null)
    103                 throw new InternalError("Shutdown hook at slot " + slot + " already registered");
    104 
    105             if (!registerShutdownInProgress) {
    106                 if (currentRunningHook >= 0)
    107                     throw new IllegalStateException("Shutdown in progress");
    108             } else {
    109                 if (VM.isShutdown() || slot <= currentRunningHook)
    110                     throw new IllegalStateException("Shutdown in progress");
    111             }
    112 
    113             hooks[slot] = hook;
    114         }
    115     }
    116 
    117 //执行真正的shutdown钩子
    118 /* Run all system shutdown hooks.
    119      *
    120      * The system shutdown hooks are run in the thread synchronized on
    121      * Shutdown.class.  Other threads calling Runtime::exit, Runtime::halt
    122      * or JNI DestroyJavaVM will block indefinitely.
    123      *
    124      * ApplicationShutdownHooks is registered as one single hook that starts
    125      * all application shutdown hooks and waits until they finish.
    126      */
    127     private static void runHooks() {
    128         synchronized (lock) {
    129             /* Guard against the possibility of a daemon thread invoking exit
    130              * after DestroyJavaVM initiates the shutdown sequence
    131              */
    132             if (VM.isShutdown()) return;
    133         }
    134 
    135         for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
    136             try {
    137                 Runnable hook;
    138                 synchronized (lock) {
    139                     // acquire the lock to make sure the hook registered during
    140                     // shutdown is visible here.
    141                     currentRunningHook = i;
    142                     hook = hooks[i];
    143                 }
    144                 if (hook != null) hook.run();
    145             } catch (Throwable t) {
    146                 if (t instanceof ThreadDeath) {
    147                     ThreadDeath td = (ThreadDeath)t;
    148                     throw td;
    149                 }
    150             }
    151         }
    152 
    153         // set shutdown state
    154         VM.shutdown();
    155     }
    156 
    157 
    158 
    159 //这个Shutdown.runHooks何时执行?
    160 /* Invoked by Runtime.exit, which does all the security checks.
    161      * Also invoked by handlers for system-provided termination events,
    162      * which should pass a nonzero status code.
    163      */
    164     static void exit(int status) {
    165         synchronized (lock) {
    166             if (status != 0 && VM.isShutdown()) {
    167                 /* Halt immediately on nonzero status */
    168                 halt(status);
    169             }
    170         }
    171         synchronized (Shutdown.class) {
    172             /* Synchronize on the class object, causing any other thread
    173              * that attempts to initiate shutdown to stall indefinitely
    174              */
    175             beforeHalt();
    176             runHooks();
    177             halt(status);
    178         }
    179     }
    180 
    181 
    182     /* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon
    183      * thread has finished.  Unlike the exit method, this method does not
    184      * actually halt the VM.
    185      */
    186     static void shutdown() {
    187         synchronized (Shutdown.class) {
    188             runHooks();
    189         }
    190     }
    191 
    192 //再进一步,这个exit/shutdown方法什么时候执行
    193 //exit方法再往上层追溯找到了我门熟悉的System.exit方法,shutdown暂时没找到调用之处
    194 
    195 //由此可见在执行System.exit方法之前会执行hooks里面所有的钩子

    shutdownHook应用场景

    很多时候,我们会有这样的一些场景,比如说nginx反向代理若干个负载均衡的web容器,又或者微服务架构中存在的若干个服务节点,需要进行无间断的升级发布。
    在重启服务的时候,除非我们去变更nginx的配置,否则重启很可能会导致正在执行的线程突然中断,本来应该要完成的事情只完成了一半,并且调用方出现错误警告。
    如果能有一种简单的方式,能够让进程在退出时能执行完当前正在执行的任务,并且让服务的调用方将新的请求定向到其他负载节点,这将会很有意义。
    自己注册ShutdownHook可以帮助我们实现java进程的平滑退出。
    设计思路:

    1. 在服务启动时注册自己的ShutdownHook
    2. ShutdownHook在被运行时,首先不接收新的请求,或者告诉调用方重定向到其他节点
    3. 等待当前的执行线程运行完毕,如果指定时间后仍在运行,则强制退出
  • 相关阅读:
    关于几种滤波的对比
    学习笔记深入理解Java中的HashMap数据结构
    学习笔记Redis基础常识
    学习笔记Java内存模型
    学习笔记理解GC
    工作中的点点滴滴单例的使用
    工作中的点点滴滴学习一下门面模式
    工作中的点点滴滴接口幂等的问题
    【转载】WCF、WebAPI、WCFREST、WebService之间的区别
    【转载】工具分享——将C#文档注释生成.chm帮助文档
  • 原文地址:https://www.cnblogs.com/alan0521/p/13173475.html
Copyright © 2020-2023  润新知