【从零单排】Java关闭钩子的应用 - Shutdown Hook
背景
在开发中,遇到这种情况,多个线程同时工作,突然一个线程遇到了fetal的错误,需要立即终止程序,等人工排查解决了问题之后重新启动。但是这样会有一个问题,程序终止时,其他线程可能正在进行重要操作,比如发一个message到另一个模块,并更新数据库状态。突然终止,可能会让这个操作只完成一半,从而导致数据不一致。
解决方案是:参考数据库Transaction原子性的概念,将这一系列重要操作看作一个整体,要么全部完成,要么全部不完成。为方便表述,我们把这一系列重要操作记为操作X。
当程序即将退出时,查看当前是否有操作X在执行中,
- 如果有,等待其完成然后退出。且期间不再接受新的操作X。如果操作X执行之间过长,终止并回滚所有状态。
- 如果没有,则可以立即退出。
在程序退出的时候,做一些Check,保证已经开始的操作X的原子性,这里就用到了Runtime.ShutdownHook
。
什么是Shutdown Hook
Shutdown hook
是一个initialized but unstarted thread。当JVM开始执行shutdown sequence时,会并发运行所有registered Shutdown Hook
。这时,在Shutdown Hook
这个线程里定义的操作便会开始执行。
需要注意的是,在Shutdown Hook
里执行的操作应当是不太耗时的。因为在用户注销或者操作系统关机导致的JVM shutdown的例子中,系统只会预留有限的时间给未完成的工作,超时之后还是会强制关闭。
什么时候会调用Shutdown Hook
- 程序正常停止
- Reach the end of program
System.exit
- 程序异常退出
- NPE
- OutOfMemory
- 受到外界影响停止
- Ctrl+C
- 用户注销或者关机
如何使用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")));
测试例子
首先,注册了一个Shutdown Hook
。
然后,系统Sleep 3秒,模拟进行某些操作。
然后,调用一个空的List,抛出异常,准备结束程序。
在程序将要结束的时候,执行Shutdown Hook
中的内容。
public static void main(String[] args)
{
// register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Do something in Shutdown Hook")));
// sleep for some time
try {
for (int i=0; i<3; i++) {
System.out.println("Count: " + i + "...");
TimeUnit.MILLISECONDS.sleep(1000);
}
List nullList = new ArrayList<>();
System.out.println("Trying to print null list's first element: " + nullList.get(0).toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Ready to exit.");
System.exit(0);
}
结果如下:
Count: 0...
Count: 1...
Count: 2...
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at HookTest.main(HookTest.java:18)
Do something in Shutdown Hook
Process finished with exit code 1
需要注意的点
- 当
System.exit
之后,当Shutdown Hook
开始执行时,其他的线程还是会继续执行。 - 应当保证
Shutdown Hook
的线程安全。 - 在使用多个
Shutdown Hook
时一定要特别小心,保证其调用的服务不会被其他Hook影响。否则会出现当前Hook所依赖的服务被另外一个Hook终止了的情况。