如何优雅的停止服务(ShutdownHook)

来源:互联网 发布:杭州seo 编辑:程序博客网 时间:2024/05/20 16:44

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方法中实现即可,更多的功能请各位自己尝试。