捕获线程运行时异常API
void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
为某个线程指定uncaughtExceptionHandler
static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
设置全局的uncaughtExceptionHandler
static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()
获取全局的uncaughtExceptionHandler
UncaughtExceptionHandler getUncaughtExceptionHandler()
获取特定线程的uncaughtExceptionHandler
UncaughtExceptionHandler介绍
线程在执行单元中是不允许抛出checked异常的,在线程的运行上下文中,派生它的线程无法直接获取它运行中得异常信息。对此java提供了UncaughtExceptionHandler接口,当线程运行过程中出现异常时会回调UncaughtExceptionHandler接口,从而得知是哪个线程运行时出了什么样的错。
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
UncaughtExceptionHandler 是一个FunctionalInterface,只有一个抽象方法,当线程运行过程中出现异常时,JVM会调用dispatchUncaughtException将对应线程的异常信息传递给回调接口。
getUncaughtExceptionHandler源码
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
方法首先会判断当前线程是否设置了uncaughtExceptionHandler ,有则执行线程自己的uncaughtException(),否则就到ThreadGroup中获取。
ThreadGroup 的uncaughtException源码
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
该线程如果有父ThreadGroup则直接调用父Group的uncaughtException(),如果设置了全局默认的UncaughtExceptionHandler,则调用uncaughtException(),既没有设置全局UncaughtExceptionHandler也没有父ThreadGroup则直接将异常信息定向到System.err中。
UncaughtExceptionHandler 测试代码
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(t.currentThread().getName()+"抛出异常 " +e.getMessage());
e.printStackTrace();
});
new Thread(()->{
System.out.println(1/0);
},"异常线程1").start();
}
输出
异常线程1抛出异常 / by zero
java.lang.ArithmeticException: / by zero
at ThreadException.ExceptionTest.lambda$1(ExceptionTest.java:11)
at java.lang.Thread.run(Unknown Source)
Hook线程介绍(钩子线程)
JVM进程的退出是由于没有活跃的非守护线程,或者收到了系统中断信号,向JVM程序注入一个Hook线程,在JVM进程退出的时候,Hook线程才会启动执行。可以通过Runtime向JVM注入多个Hook线程。
hook线程注入例子
public static void main(String[] args) {
Runtime.getRuntime()
.addShutdownHook(new Thread(()->{
System.out.println("hook test 1 running");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("hook test 1 exit");
},"HOOK线程1"));
Runtime.getRuntime()
.addShutdownHook(new Thread(()->{
System.out.println("hook test 2 running");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("hook test 2 exit");
},"HOOK线程2"));
System.out.println("main thread exit");
}
输出
main thread exit
hook test 2 running
hook test 1 running
hook test 1 exit
hook test 2 exit
Hook线程注意事项及使用场景:
- Hook线程只有在收到JVM进程退出信号的时候才会被执行。
- Hook线程可以执行一些资源释放工作,比如关闭文件句柄,socket链接等。
- 尽量不要在Hook线程中执行耗时长的操作,会导致程序迟迟不能退出。