今天就跟大家聊聊有关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如何让应用优雅的停止有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。