前言
在多线程的应用中,Hook线程使用的相对较少,然而它依然是多线程中重要的一环,尤其是在释放资源这方面,有着举足轻重的作用,希望本文能解开你心中的疑惑,助你一臂之力!
一、Hook 线程介绍
1.1、作用
如果添加了Hook线程,则JVM程序即将退出的时候(收到了中断信号),Hook 线程在JVM主线程彻底退出之前,就会被执行。
1.2、创建
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
...
//处理方法
}));
二、初识Hook线程
2.1、示例源码
package com.succ.thread.hook;
import java.util.concurrent.TimeUnit;
public class HookThreadDemo {
public static void creadHookThread(String threadName) {
Runtime.getRuntime().addShutdownHook(
new Thread(()->{
System.out.println("hook "+threadName+" is running");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hook "+threadName+" will exit");
})
);
}
public static void main(String[] args) {
System.out.println("主线程 is running");
HookThreadDemo.creadHookThread("thread1");
HookThreadDemo.creadHookThread("thread2");
System.out.println("主线程 will exit");
}
}
2.2、效果展示
可以明显看到当主线程即将退出的时候,注入的两个 Hook 线程都被启动并打印相关日志。
三、Hook 线程的应用场景
1、Hook 线程中也可以执行一些关闭、删除、释放资源的操作,比如关闭数据库连接,Socket 连接等。
2、可以做一些校验,防止程序重复执行,以及修复工作。
四、Hook 线程的注意事项
1、Hook 线程只有在正确接收到退出信号时,才能被正确执行。
通过 kill -9方式,强制杀死的进程,进程是不会去执行 Hook 线程的(自顾不暇,无力他顾)。
2、耗时的操作,不要在 Hook 线程中执行,否则可能会导致程序长时间不能退出。
3、如果有多个hook线程,则它们的执行是无序的。
五、Hook 线程实战演练
5.1、模拟生成lck文件,最后再通过Hook线程删除lck文件的示例
思路:
在程序启动时,校验是否已经生成 lock 文件。如果已经生成,则退出程序并给出异常信息提示;如果未生成,则生成 lock 文件,程序正常执行,在 JVM 退出的时候,线程中再将 lock 文件删除掉;
注:虚拟机下的CentOS、ZK、Mysql等就采用了这么个机制。
5.1.1、代码示例
package com.succ.thread.hook;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class LockFileDemo {
/** .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 线程(在程序退出时,自动删除lock文件)
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();
}
}
5.1.2、首次运行main函数后,控制台会一直打印:程序正在运行
5.1.3、点击控制台的红色按钮,或kill -9 强行关闭线程
5.1.4、再次重新运行main函数,则给出提示
5.1.5、在工程源码根目录找到.lock文件,手动删除,再次run main函数
删除lock文件后,发现又可以愉快的玩耍了
后记
文章核心源码来自异常教程,它的开发环境是Linux,本文基于此,以Windows环境为依托,并做了一些完善和优化!
总结
本文详细的介绍了Hook钩子线程是使用,并提供了2个精简的小案例,它的创建和使用都比较简单,重点要记住它的使用场景和注意事项,让我们在合适的开发环境,能够锦上添花。
尾言
不是在学习中前进,就是在前进中摸索,学无止境,每天一个新发现,每天进步一点点,努力少年!
附注
1、JAVA多线程:synchronized理论和用法 | Lock和ReentrantLock Volatile 区别和联系(一)
2、JAVA多线程:yield/join/wait/notify/notifyAll等方法的作用(二)
3、JAVA多线程:狂抓!join()方法到底会不会释放锁,给你彻底介绍清楚(三)
4、JAVA多线程:sleep(0)、sleep(1)、sleep(1000)的区别(四)
5、JAVA多线程:揭秘thread.setDaemon(true) | thread.isAlive守护线程的使用(五)