多线程(6)- Hook线程与线程异常捕获
前言
Hook钩子,线程异常扩展:
概念
- UncaughtExceptionHandler: 线程在执行单元(run部分)是不允许抛出异常的,而且线程运行在自己的上下文中,派生它的线程将无法直接获得它运行中出现的异常信息,对此JAVA提供了UncaughtExceptionHandler接口,当线程运行中出现异常,会回调UncaughtExceptionHandler接口,告诉是哪个线程出现什么错误。
- Hook钩子:作为程序运行结束时的线程,处理后续工作
1、UncaughtExceptionHandler
API
//特定线程设置全局的UncaughtExceptionHandler
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
//设置全局的UncaughtExceptionHandler
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
//获取全局的UncaughtExceptionHandler
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
//获取特定线程的UncaughtExceptionHandler
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
dispatchUncaughtException-异常回调
//仅由JVM调用此方法处理异常
/**
* Dispatch an uncaught exception to the handler. This method is
* intended to be called only by the JVM.
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
2、获取线程运行时异常
/**
* 使用钩子 处理异常
*/
private static void uncaughtException() {
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(t);
e.printStackTrace();
});
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(1/0);
},"thread_uncaught").start();
}
Thread[thread_uncaught,5,main]
java.lang.ArithmeticException: / by zero
at com.today.roc.go.understand.thread.six.UncaughtExceptionMain.lambda$main$1(UncaughtExceptionMain.java:28)
at java.lang.Thread.run(Thread.java:748)
3、异常捕获机制
Thread捕获机制
如果当前线程没有UncaughtExceptionHandler,则线程组处理
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
ThreadGroup异常捕获机制
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
//父类不为空,使用父UncaughtExceptionHandler
parent.uncaughtException(t, e);
} else {
//获取全局UncaughtExceptionHandler
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
//交给System.err处理
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
异常出现uncaught调用流程:Thread -> ThreadGroup -> MainGroup -> SystemGroup ->System.err
private static void testNoUncaught(){
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
System.out.println(mainGroup);
System.out.println(mainGroup.getParent());
System.out.println(mainGroup.getParent().getParent());
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(1/0);
},"uncaught_thread").start();
}
java.lang.ThreadGroup[name=main,maxpri=10]
java.lang.ThreadGroup[name=system,maxpri=10]
null
Exception in thread "uncaught_thread" java.lang.ArithmeticException: / by zero
at com.today.roc.go.understand.thread.six.UncaughtExceptionMain.lambda$testNoUncaught$2(UncaughtExceptionMain.java:51)
at java.lang.Thread.run(Thread.java:748)
4、注入钩子线程
当JVM程序停止时,会启动Hook线程,进行事后处理
防止程序重复启动:启动时候创建一个lock文件,添加一个hook当程序停止时删除lock文件,如果文件存在表示程序运行,不能重复启动。
public class HookMain {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(
() -> {
try {
System.out.println("The hook thread 1 is running.");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("The hook thread 1 will exit.");
}
));
Runtime.getRuntime().addShutdownHook(new Thread(
() -> {
try {
System.out.println("The hook thread 2 is running.");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("The hook thread 2 will exit.");
}
));
System.out.println("The program will is stopping");
}
}
The program will is stopping
The hook thread 2 is running.
The hook thread 1 is running.
The hook thread 2 will exit.
The hook thread 1 will exit.
防止程序重复启动
/**
* Hook实战
*/
public static void hook() throws IOException, InterruptedException {
Runtime.getRuntime().addShutdownHook(new Thread(()->{
System.out.println(" program has exit ");
if (getLookFile().toFile().exists()){
//删除文件
getLookFile().toFile().delete();
}
}));
checkRunning();
IntStream.range(0,5).forEach(v->{
System.out.println(" the program is running ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
/**
* 检查文件是否存在,如果是第一次启动,创建文件
*/
public static void checkRunning() throws IOException {
Path path = getLookFile();
if (path.toFile().exists()) {
//文件存在,表示已经启动
throw new RuntimeException("the program already running……");
}
Files.createFile(path);
System.out.println("create file success");
}
public static Path getLookFile() {
return Paths.get(LOCK_PATH, LOCK_FILE);
}