利用 java.lang.Runtime.addShutdownHook() 钩子程序,保证java程序安全退出

  今天在开发时,看到如下这段代码:

private void startServer() throws Exception {
    // register shutdownhook
    try {
        doStart();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                try {
                    stopServer();
                } catch (Exception e) {
                    logger.error("shutdown error", e);
                }
            }
        });
        isShutDown = false;
        logger.info("Server start now !!!");

        this.await();
    } catch (Throwable e) {
        e.printStackTrace();
        this.stopServer();
        throw e;
    }
}

  第一次见到Runtime.getRuntime().addShutdownHook,查了一下资料,原来该方法用来在jvm中增加一个关闭的钩子。当程序正常退出,系统调用 System.exit方法或虚拟机被关闭时才会执行添加的shutdownHook线程。其中shutdownHook是一个已初始化但并不有启动的线程,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以可通过这些钩子在jvm关闭的时候进行内存清理、资源回收等工作。  

实例测试一:

public class TestRuntimeShutdownHook {
    
    public static void main(String[] args) {

        Thread shutdownHookOne = new Thread() {
            public void run() {
                System.out.println("shutdownHook one...");
            }
        };
        Runtime.getRuntime().addShutdownHook(shutdownHookOne);

        Runnable threadOne = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread one doing something...");
            }
        };

        Runnable threadTwo = new Thread() {
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread two doing something...");
            }
        };

        threadOne.run();
        threadTwo.run();
    }
}

  运行结果:

thread one doing something...
thread two doing something...
shutdownHook one...

实例测试二:下面实例程序在执行过程中按下ctrl -c或者 kill -15,由于钩子程序的循环检测,能够保证线程执行完毕后,程序才关闭。

public class TestShutdownHook {

    /**
     * 测试线程,用于模拟一个原子操作
     */
    private static class TaskThread extends Thread {
        
        @Override
        public void run() {
            System.out.println("thread begin ...");
            TestShutdownHook.sleep(1000);
            System.out.println("task 1 ok ...");
            TestShutdownHook.sleep(1000);
            System.out.println("task 2 ok ...");
            TestShutdownHook.sleep(1000);
            System.out.println("task 3 ok ...");
            TestShutdownHook.sleep(1000);
            System.out.println("task 4 ok ...");
            TestShutdownHook.sleep(1000);
            System.out.println("task 5 ok ...");

            System.out.println("thread end\n\n");
        }
    }

    /**
     * 注册hook程序,保证线程能够完整执行
     * @param checkThread
     */
    private static void addShutdownHook(final Thread checkThread) {
        //为了保证TaskThread不在中途退出,添加ShutdownHook
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                System.out.println("收到关闭信号,hook起动,开始检测线程状态 ...");
                //不断检测一次执行状态,如果线程一直没有执行完毕,超时后,放弃等待       \
                for (int i = 0; i < 100; i++) {
                    if (checkThread.getState() == State.TERMINATED) {
                        System.out.println("检测到线程执行完毕,退出hook");
                        return;
                    }
                    TestShutdownHook.sleep(100);
                }
                System.out.println("检测超时,放弃等待,退出hook,此时线程会被强制关闭");
            }
        });
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final TaskThread taskThread = new TaskThread();
        //为了保证TaskThread不在中途退出,添加ShutdownHook
        addShutdownHook(taskThread);
        //执行TaskThread
        taskThread.start();
    }
}

  让它正常运行结束,输出如下:

thread begin ...
task 1 ok ...
task 2 ok ...
task 3 ok ...
task 4 ok ...
task 5 ok ...
thread end


收到关闭信号,hook起动,开始检测线程状态 ...
检测到线程执行完毕,退出hook

  以前在开发时只知道依靠数据库事务来保证程序关闭时数据的完整性。

  但有些时候一个业务上要求的原子操作,不一定只包括数据库,比如外部接口或者消息队列。此时数据库事务就无能为力了。这时我们可以依靠java提供的一个工具方法:java.lang.Runtime.addShutdownHook(Thread hook)。
  addShutdownHook方法可以加入一个钩子,在程序退出时触发该钩子。(退出是指ctrl+c或者kill -15,但如果用kill -9 那是没办法的,具体有关kill的signal机制有篇大牛的文章《Linux 信号signal处理机制》),钩子做什么操作都可以,甚至可以循环检查某个线程的状态,直到业务线程正常退出,再结束钩子程序就可以保证业务线程的完整性。

 

参考文章:http://www.cnblogs.com/baibaluo/p/3185925.html

http://kim-miao.iteye.com/blog/1662550

posted on 2017-07-05 21:40  bijian1013  阅读(410)  评论(0)    收藏  举报

导航