在一些Java程序中,尤其是数据相关的应用程序中,正确地关闭应用程序非常地重要,因为突然地或者意外地关闭程序往往意味着数据地丢失。因此,要确保在关闭程序之前,做一些收尾工作,比如关闭数据库连接,提交事务或者保存文件等等。在Java中可以用Runtime.addShutdownHook()来完成上述任务,但是要正确地使用这个方法其实并不容易。
该方法的基本形式如下:
public void addShutdownHook(Thread hook);
其中hook是一个包含了执行shutdown任务的Runnable线程,在Apache Kafka的官方文档上就有一个通过shutdownHook关闭流的示例:
// Add shutdown hook to stop the Kafka Streams threads.// You can optionally provide a timeout to `close`.Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
这个方法确保在应用程序关闭前先关闭流,记录好已消费的offset,下次启动能从断点重新开始。
Linux信号量
要用好shutdownHook,要从Linux的信号量说起。信号量是Linux操作系统提供的一种机制,用于向Linux进程发出信号。具体的我们可以通过man kill命令来进行查看:
KILL(1) BSD General Commands Manual KILL(1)NAMEkill -- terminate or signal a processSYNOPSISkill [-s signal_name] pid ...kill -l [exit_status]kill -signal_name pid ...kill -signal_number pid ...DESCRIPTIONThe kill utility sends a signal to the processes specified by the pid operands.Only the super-user may send signals to other users processes.
常见的信号可以通过kill -l进行查看:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL5) SIGTRAP 6) SIGABRT 7) SIGEMT 8) SIGFPE9) SIGKILL 10) SIGBUS 11) SIGSEGV 12) SIGSYS13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGURG17) SIGSTOP 18) SIGTSTP 19) SIGCONT 20) SIGCHLD21) SIGTTIN 22) SIGTTOU 23) SIGIO 24) SIGXCPU25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH29) SIGINFO 30) SIGUSR1 31) SIGUSR2
上面所有的信号量只有SIGINT(kill -2),SIGKILL(kill -9)以及SIGTERM(kill -15)是保证被执行的,其它所有的信号都是可以被忽略的。
可以参考下面的链接:
1. 不要使用kill -9来结束你的应用程序
kill -9是操作系统的终极杀器,如果你使用kill -9的话,应用程序是没有任何发言权的,它只能选择默默退出,几乎可以肯定,shutdownHook不会被调用。
2. shutdownHook涉及的方法应该尽量的短
这个可能和操作系统有关系,不同的操作系统可能有不同的差异:
When a computer shuts down, the final stage of the shutdown process sends every remaining process a SIGTERM, gives those processes a few seconds grace, then sends them a SIGKILL.
也就是说,如果shutdownHook过长,可能方法还没执行完,进程就被操作系统强制杀掉了,这一点在addShutdownHook()的文档上也有提及:
* <p> Shutdown hooks should also finish their work quickly. When a* program invokes {@link #exit exit} the expectation is* that the virtual machine will promptly shut down and exit. When the* virtual machine is terminated due to user logoff or system shutdown the* underlying operating system may only allow a fixed amount of time in* which to shut down and exit. It is therefore inadvisable to attempt any* user interaction or to perform a long-running computation in a shutdown* hook.
3. shutdownHook的方法应该是线程安全的
这是因为,用户可能多次发送信号导致方法被不同的线程被多次调用,关于这一点文档也有说明:
* They should, in* particular, be written to be thread-safe and to avoid deadlocks insofar* as possible.
4. 关于shutdownHook方法的异常
shutdownHook调用过程中产生的所有异常都会被忽略掉并且可能不会输出任何提示信息,因此程序可能蕴含了一个久久不能被发现的BUG导致你的shutdownHook无法被执行,在调用shutdownHook的过程中,一定要仔细检查你的代码,保证正确性。
5. 某些场景下要提供at most once的保证
这点其实是接第三点说的,就是你的shutdownHook可能被调用多次,但其实关闭一次就够了,多次调用可能会引发一些意想不到的异常。比如KafkaStream的close方法,就提供了这样的保证:
public synchronized boolean close(long timeout, TimeUnit timeUnit) {this.log.debug("Stopping Streams client with timeoutMillis = {} ms.", timeUnit.toMillis(timeout));if (!this.setState(KafkaStreams.State.PENDING_SHUTDOWN)) {this.log.info("Already in the pending shutdown state, wait to complete shutdown");} else {// ....
可以使用CAS操作来做这样的检查:
if (state.compareAndSet(ACTIVE, CLOSED)) {// close here}
以上就是我在使用shutdownHook的过程中总结的一些点;同时强烈建议大家在使用shutdownHook前仔细阅读该方法的文档以及我前面提到的Stack Overflow上的回答。
暂无评论数据