Archive for November, 2010

How to exit a Java app gracefully

标题:如何优雅的退出Java程序
本文中信号拦截代码来自下面的博客:
http://twit88.com/blog/2007/09/27/do-a-graceful-shutdown-of-your-java-application-when-ctr-c-kill/

在UNIX下,通常我们结束一个进程,最常用的方式就是使用kill –9 <PID>,这个方法最简单,但是会带来一个问题,程序可能正在执行某些操作,而这些操作不能被突然中断,否则会造成一些资源的泄露,或者数据完整性的问题。

在UNIX下,信号可以被用来解决这个问题,我们应该比较熟悉kill这个命令,通常使用的命令kill –9 中的kill其实不是‘杀死’的意思,而是‘发送’。kill –9 就是把 SIGKILL(数字代码为9)的信号发送到目标进程。在Java中,可以捕获这些信号:

Runtime.getRuntime().addShutdownHook(…);

当我们执行Ctrl+C或者kill命令的时候,信号Handler会被通知到,这个Handler在一个新的线程中(并非主线程),我们可以在这个Handler里面处理资源释放等问题,注意这里的kill没有带参数,其实等同于kill -15,如果使用kill -9就会直接干掉目标进程,资源得不到回收。具体做法可参考上面的链接。

当Java捕捉到SIGTERM信号的时候,它会执行子线程中的shutdown(); 代码,理论上我们可以把资源清理工作放在这里,我们知道,这个信号监听的线程和主线程,分别在处在两个线程中,这里的代码执行完毕后,它并不管主线程里面的代码是否执行完毕,直接就把整个进程结束了。假设我们有一个长循环在主线程中,它可能会在循环没有执行完毕就殒命了。这个不是我们想要的,解决也很简单:在两个线程中增加同步机制就可以了。

尝试一:
设置一个标志位(notifiedMainThreadToExit = false),当收到SIGTERM信号后,在子线程中设置notifiedMainThreadToExit为true,然后sleep(Long.MAX_VALUE),主线程轮询这个标识位,一旦发现该变量为true,就退出循环,并且释放资源。

问题出现了:主线程的确退出了,可是子线程一直长眠,这个会导致整个进程都不会退出。后来我在主线程退出前,执行System.exit,问题依旧。理论上:System.exit执行后,它会结束该虚拟机中的所有线程,不知道为什么在这里行不通。

尝试二:
设置两个标识位(notifiedMainThreadToExit = false, notifiedSignalHandlerToExit = false),当子线程收到信号后,它通知主线程(set notifiedMainThreadToExit = true):“我收到SIGTERM信号了,你可以准备收工了”,然后,子线程并不马上退出,也不永久休眠,而是轮训等待主线程通知自己。当主线程接受到子线程的通知后,退出循环,然后通知子线程(set notifiedSignalHandlerToExit = true):“我已经准备好了,你也可以收工了”,这样两个线程都可以正常的退出。

结论:
尝试二是可行的,且两个线程间不会产生Race Condition,因为没有两个线程同时写的问题,所以很安全。这里可以下载例子。最后提醒,文章提到的kill,是不带参数的,不要用kill -9。

[ad]

Switch to our mobile site