![536a25f5d08803cbf34166472158e6a9.png](https://i-blog.csdnimg.cn/blog_migrate/32ed99dc124de07b420eb47c0e445e50.jpeg)
文章首发自个人微信号: 小哈学Java
个人网站地址: https://www. exception.site/java-con currency/java-concurrency-hook-thread
目录
- 一、Hook 线程介绍
- 二、Hook 线程的应用场景&注意事项
- 三、Hook 线程防应用重启实战
- 四、GitHub 源码地址
- 五、总结
一、Hook 线程介绍
通常情况下,我们可以向应用程序注入一个或多个 Hook (钩子) 线程,这样,在程序即将退出的时候,也就是 JVM 程序即将退出的时候,Hook 线程就会被启动执行。
先看一段示例代码:
![f9519e4d5147b37457ce7c6abe32d243.png](https://i-blog.csdnimg.cn/blog_migrate/a550b3de1e705ed15d25178258bf1540.jpeg)
- ①:为应用程序注入一个钩子(Hook)线程,线程中,打印了相关日志,包括正在运行以及退出的日志;
- ②:再次注入一个同样逻辑的钩子(Hook)线程;
- ③:主线程执行结束,打印日志;
运行这段代码,来验证一下:
![84c1c010d017b46e4643b07bd08e7d0a.png](https://i-blog.csdnimg.cn/blog_migrate/5340291874057660bb7a2b934e5c9e2c.jpeg)
从打印日志看到,当主线程执行结束,也就是 JVM 进程即将退出的时候,注入的两个 Hook 线程都被启动并打印相关日志。
二、Hook 线程的应用场景&注意事项
2.1 应用场景
上面我们已经知道了, Hook 线程能够在 JVM 程序退出的时候被启动且执行,那么,我们能够通过这种特性,做点什么呢?
罗列一些常见应用场景:
- 防止程序重复执行,具体实现可以在程序启动时,校验是否已经生成 lock 文件,如果已经生成,则退出程序,如果未生成,则生成 lock 文件,程序正常执行,最后再注入 Hook 线程,这样在 JVM 退出的时候,线程中再将 lock 文件删除掉;
![931d2d9c6a70ac80e5f07bb131c3ff81.png](https://i-blog.csdnimg.cn/blog_migrate/d8252a32983274bc89ec1ce8ad230e64.jpeg)
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();
}
}
运行程序,控制台输出如下:
![27087929d5cb397085d894c0c3d7d650.png](https://i-blog.csdnimg.cn/blog_migrate/9e06eeb4959a0e40a07271fbd5b05d62.jpeg)
程序一直运行中,再来看下 .lock
文件是否生成:
![e9e7c1f5f24d90ecc93aded4f69090a5.png](https://i-blog.csdnimg.cn/blog_migrate/ce8f3e0c016a0753023d3aec3d352809.jpeg)
文件生成成功,接下来,我们再次运行程序,看看是否能够重复启动:
![e8d3d45ac3f7d8e8fac3c40c41644ec7.png](https://i-blog.csdnimg.cn/blog_migrate/5e8b266ccedcdcec256c306b977c6291.jpeg)
可以看到,无法重复运行程序,且抛出了 The program already running.
的运行时异常。接下来,通过 kill pid
或者 kill -l pid
命令来结束进程:
![cbe330f6a7a04a9528ff6016d4cee5eb.png](https://i-blog.csdnimg.cn/blog_migrate/6296439103e751290120c542ad9f8b7a.jpeg)
程序在即将退出的时候,启动了 Hook 线程,在看下 .lock
文件是否已被删除:
![a5077cce4d50a35abcd72f2170725929.png](https://i-blog.csdnimg.cn/blog_migrate/229c90f8337f38bcc1584dfd2e74ff13.jpeg)
到此,Hook 线程代码实战部分结束了。
四、GitHub 源码地址
https://github.com/weiwosuoai/java-concurrent-tutorial
五、总结
本文中,我们学习了什么是 Hook (钩子) 线程,相关应用场景以及注意事项。祝你学习愉快 !
六、Ref
- 《Java 高并发编程详解》
欢迎关注微信公众号: 小哈学Java
![2f26e69314e075621dc3f06fe42c1ff1.png](https://i-blog.csdnimg.cn/blog_migrate/d3fa41a7ec5fccb13e5111f2442696ae.jpeg)