一, Hook 线程介绍
通常情况下, 我们可以向应用程序注入一个或多个 Hook (钩子) 线程, 这样, 在程序即将退出的时候, 也就是 JVM 程序即将退出的时候, Hook 线程就会被启动执行.
先看一段示例代码:
示例代码
1: 为应用程序注入一个钩子 (Hook) 线程, 线程中, 打印了相关日志, 包括正在运行以及退出的日志;
2: 再次注入一个同样逻辑的钩子 (Hook) 线程;
3: 主线程执行结束, 打印日志;
运行这段代码, 来验证一下:
Hook 线程执行结果
从打印日志看到, 当主线程执行结束, 也就是 JVM 进程即将退出的时候, 注入的两个 Hook 线程都被启动并打印相关日志.
二, Hook 线程的应用场景 & 注意事项
2.1 应用场景
上面我们已经知道了, Hook 线程能够在 JVM 程序退出的时候被启动且执行, 那么, 我们能够通过这种特性, 做点什么呢?
罗列一些常见应用场景:
防止程序重复执行, 具体实现可以在程序启动时, 校验是否已经生成 lock 文件, 如果已经生成, 则退出程序, 如果未生成, 则生成 lock 文件, 程序正常执行, 最后再注入 Hook 线程, 这样在 JVM 退出的时候, 线程中再将 lock 文件删除掉;
流程图
PS: 这种防止程序重复执行的策略, 也被应用于 MySQL 服务器, zookeeper, kafka 等系统中.
Hook 线程中也可以执行一些资源释放的操作, 比如关闭数据库连接, Socket 连接等.
2.2 注意事项
Hook 线程只有在正确接收到退出信号时, 才能被正确执行, 如果你是通过 kill -9 这种方式, 强制杀死的进程, 那么抱歉, 进程是不会去执行 Hook 线程的, 为什么呢? 你想啊, 它自己都被强制干掉了, 哪里还管的上别人呢?
请不要在 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 线程代码实战部分结束了.
来源: http://www.jianshu.com/p/dc35d531a585