美文网首页Java 杂谈Java 并发教程
面试官: 说说看, 什么是 Hook (钩子) 线程以及应用场景

面试官: 说说看, 什么是 Hook (钩子) 线程以及应用场景

作者: 插件小屋 | 来源:发表于2019-04-11 20:16 被阅读2次
    Hook 钩子线程

    文章首发自个人微信号: 小哈学Java

    个人网站地址: https://www.exception.site/java-concurrency/java-concurrency-hook-thread

    目录

    • 一、Hook 线程介绍

    • 二、Hook 线程的应用场景&注意事项

    • 三、Hook 线程防应用重启实战

    • 四、GitHub 源码地址

    • 五、总结

    一、Hook 线程介绍

    通常情况下,我们可以向应用程序注入一个或多个 Hook (钩子) 线程,这样,在程序即将退出的时候,也就是 JVM 程序即将退出的时候,Hook 线程就会被启动执行

    先看一段示例代码:

    示例代码
    • :为应用程序注入一个钩子(Hook)线程,线程中,打印了相关日志,包括正在运行以及退出的日志;
    • :再次注入一个同样逻辑的钩子(Hook)线程;
    • :主线程执行结束,打印日志;

    运行这段代码,来验证一下:

    Hook 线程执行结果

    从打印日志看到,当主线程执行结束,也就是 JVM 进程即将退出的时候,注入的两个 Hook 线程都被启动并打印相关日志。

    二、Hook 线程的应用场景&注意事项

    2.1 应用场景

    上面我们已经知道了, Hook 线程能够在 JVM 程序退出的时候被启动且执行,那么,我们能够通过这种特性,做点什么呢?

    罗列一些常见应用场景:

    1. 防止程序重复执行,具体实现可以在程序启动时,校验是否已经生成 lock 文件,如果已经生成,则退出程序,如果未生成,则生成 lock 文件,程序正常执行,最后再注入 Hook 线程,这样在 JVM 退出的时候,线程中再将 lock 文件删除掉;
    流程图

    PS: 这种防止程序重复执行的策略,也被应用于 Mysql 服务器,zookeeper, kafka 等系统中。

    1. Hook 线程中也可以执行一些资源释放的操作,比如关闭数据库连接,Socket 连接等。

    2.2 注意事项

    1. Hook 线程只有在正确接收到退出信号时,才能被正确执行,如果你是通过 kill -9这种方式,强制杀死的进程,那么抱歉,进程是不会去执行 Hook 线程的,为什么呢?你想啊,它自己都被强制干掉了,哪里还管的上别人呢?
    2. 请不要在 Hook 线程中执行一些耗时的操作,这样会导致程序长时间不能退出。

    三、Hook 线程防应用重启实战

    针对上面防应用重启的场景,利用 Hook 线程,我们来实战一下,贴上代码:

    import java.io.File;
    import java.io.IOException;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 犬小哈(微信号: 小哈学Java)
     * @date 2019/4/10
     * @time 下午9:56
     * @discription
     **/
    public class PreventDuplicated {
    
        /** .lock 文件存放路径 */
        private static final String LOCK_FILE_PATH = "./";
        
        /** .lock 文件名称 */
        private static final String LOCK_FILE_NAME = ".lock";
    
        public static void main(String[] args) {
    
            // 校验 .lock 文件是否已经存在
            checkLockFile();
    
            // 注入 Hook 线程
            addShutdownHook();
    
            // 模拟程序一直运行
            for (;;) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("The program is running ...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
        /**
         * 注入 Hook 线程
         */
        private static void addShutdownHook() {
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                // 接受到了退出信号
                System.out.println("The program received kill signal.");
                // 删除 .lock 文件
                deleteLockFile();
            }));
        }
    
        /**
         * 校验 .lock 文件是否已经存在
         */
        private static void checkLockFile() {
            if (isLockFileExisted()) {
                // .lock 文件已存在, 抛出异常, 退出程序
                throw new RuntimeException("The program already running.");
            }
    
            // 不存在,则创建 .lock 文件
            createLockFile();
        }
    
        /**
         * 创建 .lock 文件
         */
        private static void createLockFile() {
            File file = new File(LOCK_FILE_PATH + LOCK_FILE_NAME);
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * .lock 文件 是否存在
         * @return
         */
        private static boolean isLockFileExisted() {
            File file = new File(LOCK_FILE_PATH + LOCK_FILE_NAME);
            return file.exists();
        }
    
        /**
         * 删除 .lock 文件
         */
        private static void deleteLockFile() {
            File file = new File(LOCK_FILE_PATH + LOCK_FILE_NAME);
            file.delete();
        }
    }
    
    

    运行程序,控制台输出如下:

    控制台输出

    程序一直运行中,再来看下 .lock 文件是否生成:

    lock 文件

    文件生成成功,接下来,我们再次运行程序,看看是否能够重复启动:

    重复启动程序,抛出异常

    可以看到,无法重复运行程序,且抛出了 The program already running. 的运行时异常。接下来,通过 kill pid 或者 kill -l pid 命令来结束进程:

    Hook 线程被启动了

    程序在即将退出的时候,启动了 Hook 线程,在看下 .lock 文件是否已被删除:

    .lock 文件被删除了

    到此,Hook 线程代码实战部分结束了。

    四、GitHub 源码地址

    https://github.com/weiwosuoai/java-concurrent-tutorial

    五、总结

    本文中,我们学习了什么是 Hook (钩子) 线程,相关应用场景以及注意事项。祝你学习愉快 !

    六、Ref

    • 《Java 高并发编程详解》

    赠送 | 面试&学习福利资源

    最近在网上发现一个不错的 PDF 资源《Java 核心面试知识.pdf》分享给大家,不光是面试,学习,你都值得拥有!!!

    获取方式: 关注公众号: 小哈学Java, 后台回复 资源,既可获取资源链接,下面是目录以及部分截图:

    福利资源截图 福利资源截图 福利资源截图 福利资源截图 福利资源截图 福利资源截图 福利资源截图

    重要的事情说两遍,获取方式: 关注公众号: 小哈学Java, 后台回复 资源,既可获取资源链接 !!!

    欢迎关注微信公众号: 小哈学Java

    小哈学Java

    相关文章

      网友评论

        本文标题:面试官: 说说看, 什么是 Hook (钩子) 线程以及应用场景

        本文链接:https://www.haomeiwen.com/subject/ggnxwqtx.html