温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

ShutdownHook如何让应用优雅的停止

发布时间:2022-01-04 18:10:26 来源:亿速云 阅读:145 作者:柒染 栏目:大数据

今天就跟大家聊聊有关ShutdownHook如何让应用优雅的停止,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

对于应用,停止的时候一般都会把环境和数据恢复到运行前的样子,和单元测试时tearDown要做的效果基本类似。而为了实现停止时恢复现场,英文中有个特定的描述:

shutdown gracefully

在Java中,为了实现gracefully shutdown,有一个特定的接口可供使用,就是我们今天要提到的shutdown hook。它与回调函数功能类似,文档中对其描述如下:

关闭钩子 只是一个已初始化但尚未启动的线程。虚拟机开始启用其关闭序列时,它会以某种未指定的顺序启动所有已注册的关闭钩子,并让它们同时运行。运行完所有的钩子后,如果已启用退出终结,那么虚拟机接着会运行所有未调用的终结方法。最后,虚拟机会暂停。注意,关闭序列期间会继续运行守护线程,如果通过调用 exit 方法来发起关闭序列,那么也会继续运行非守护线程。

要为应用添加shutdownHook,需要做的只是这样的下操作:

Runtime.getRuntime().addShutdownHook(new Thread() {
 
    public void run() { /*
       my shutdown code here
    */ }
 });

向addShutdownHook方法传入的Thread,其run方法即为自定义的shutdown时清理逻辑。

JDK内部,是通过一个Map来保存所有添加的ShutdownHook,在被触发时执行。

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

 
static synchronized void add(Thread hook) {
    hooks.put(hook, hook);
}
 

 
private static IdentityHashMap<Thread, Thread> hooks;
 
hooks = new IdentityHashMap<>();

那这个shutdownHook一般是在什么时候会被调用呢?

我们看JDK的文档中描述,对于两类事件Java虚拟机会退出:

Java 虚拟机会为了响应以下两类事件而关闭

  • 程序正常退出,这发生在最后的非守护线程退出时,或者在调用 exit(等同于 System.exit)方法时。或者,

  • 为响应用户中断而终止 虚拟机,如键入 ^C;或发生系统事件,比如用户注销或系统关闭。 

在Java虚拟机退出的时候,这些设置的shutdownHook会被并行的调用。但需要注意的是,对于非正常方式退出Java虚拟机,例如杀进程,系统断电等,这些情况下,shutdownHook不会被执行。

我们来看,在Tomcat内部,是如何使用ShutdownHook的。

Catalina类内部,在服务器内部各个组件启动完毕后,有这样一段代码


 
// Register shutdown hook
if (useShutdownHook) {
   if (shutdownHook == null) {
       shutdownHook = new CatalinaShutdownHook();
   }
   Runtime.getRuntime().addShutdownHook(shutdownHook);
 
}

和我们在本文开头看到的一样,他注册了一个ShutdownHook。这个hook的内容如下:


 
/**
* Shutdown hook which will perform a clean shutdown of Catalina if needed.
*/
protected class CatalinaShutdownHook extends Thread {
   public void run() {
       try {
           if (getServer() != null) {
               Catalina.this.stop();
           }
       } catch (Throwable ex) {
       } finally {
       }
   }
}

看就是在shutdown的时候通过调用应用服务器的stop方法,来shutdown gracefully。

而我们一般停止Tomcat会通过调用shutdown脚本的方式进行,这个时候,脚本实质上去执行了应用服务器的stop方法来进行停止,而不是被shutdownHook来反调过来的。

因此,在读源码时,你会发现代码中有这样的内容:

/**
* Stop an existing server instance.
*/
public void stop() {

   try {
       // Remove the ShutdownHook first so that server.stop()
       // doesn't get invoked twice
       if (useShutdownHook) {
        Runtime.getRuntime().removeShutdownHook(shutdownHook);  
   } catch (Throwable t) {
       ExceptionUtils.handleThrowable(t);}

我们看到,在停止Server的时候,会先执行removeShutdownHook的操作,注释里写的明白,是为了防止server和stop被执行两次。

哪种情况下会被执行两次呢?

当使用catalina的shutdown脚本停止Server时,这个方法并不是从shutdownHook调用过来,此时,shutdownHook还没有执行,所以在JVM退出的时候,shutdownHook会被触发。如果此处还没去remove掉的话,就还会调用过来。这一点在我们自己开发应用时需要注意借鉴一下。

看到这里,希望你不要说然并卵。

当然,如果你已经脱口而出,那...

我找了例子证明,你已经在不知不觉中使用了shutdownHook,例如你在输出日志的时候,以下代码是JDK的LogManager中的一部分,我们看到,其构造方法中直接会添加一个名为Cleaner的shutdownHook。

private LogManager(Void checked) {
   // Add a shutdown hook to close the global handlers.
   try {
       Runtime.getRuntime().addShutdownHook(new Cleaner());
   } catch (IllegalStateException e) {
       // If the VM is already shutting down,
       // We do not need to register shutdownHook.
   }
}
 

public void run() {

// This is to ensure the LogManager.<clinit> is completed
   // before synchronized block. Otherwise deadlocks are possible.
   LogManager mgr = manager;
   // Do a reset to close all active handlers.
   reset();
}

我们自己添加shutdownHook的时候,需要注意的是:

在内部不要写耗时的操作,也不要写容易引起死锁的操作。多个ShutdownHook之间如果存在资源竞争而死锁,那应用就停止不了了。

看完上述内容,你们对ShutdownHook如何让应用优雅的停止有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI