JDK提供了Runtime.addShutdownHook(Thread hook)方法用来注册一个钩子(线程),在Java程序退出时会调用这个钩子来清理现场。
这个钩子会在以下场景中被调用:
1. 程序正常退出
2. 使用System.exit()
3. 终端使用Ctrl+C触发的中断
4. 系统关闭
5. OutOfMemory宕机
6. 使用Kill pid命令干掉进程(注:在使用kill -9 pid时,是不会被调用的)
下面我们来简单的模拟一个服务,详细说明如何优雅的终止Java进程。
/**
* zyc 2017年10月16日 下午3:05:03
*/
public class ShutdownHookTest{
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
private static final long TIMEOUT = 10 * 1000;
public static void main(String[] args) {
// 注册钩子函数,在JVM接收到停止指令后会运行该线程
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("invoke shutdownhook ....");
// 调用该方法把threadpool 的 shutdown 设置为true,不再接受提交任务,
// 等待已经提交的任务执行完之后才会退出
executorService.shutdown();
long time = System.currentTimeMillis();
while(true){
int activeCount = ((ThreadPoolExecutor)executorService).getActiveCount(); // 执行中的线程数
int waitCount = ((ThreadPoolExecutor)executorService).getQueue().size(); // 等待的线程数
System.out.println("there are " + waitCount + " threads wait ....");
System.out.println("there are " + activeCount + " threads working ....");
if(executorService.isTerminated()){ // 等待线程池退出
System.out.println("thread pool is shutdown ...");
break;
}
if(System.currentTimeMillis() - time > TIMEOUT) // 超时
executorService.shutdownNow();
sleep(500);
}
}
}));
// 模拟工作环境,不断往线程池提交任务
for(;;){
if(executorService.isShutdown())
break;
executorService.submit(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " work ....");
sleep(2 * 1000); // 模拟线程工作
}
});
sleep(500);
}
}
private static void sleep(long millis){
try {
TimeUnit.MILLISECONDS.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
把代码打成Jar包在linux上运行 java –jar ShutdownHookTest.jar,输出如下:
线程池在持续工作,并不断的有任务提交。
执行kill命令终止Java进程
注意:这里用的是kill -15 pid 不是kill -9 pid
Java进程接收到终止指令后,调用钩子方法,直到线程池里的所有任务都执行完毕才退出
本文简单的模拟了关闭服务的场景,通过不断的轮询线程池的状态,直到所有提交的任务执行完毕才终止进程,清理的逻辑在钩子函数的run方法中实现即可,更多的功能请各位自己尝试。