UncaughtExceptionHandler 获取线程运行时异常
我们知道线程执行体的方法接口定义为:public void run(),因此线程在执行单元中是不允许抛出checked异常的,且线程之间是相对独立的,他们运行在自己的上下文当中,派生它的线程无法直接感知到它在运行时出现的异常信息。为了解决这个问题,java提供了UncaughtExceptionHandler接口,当线程在运行时发生异常时,会回调这个接口,从而得知哪个线程在运行时出错。
UncaughtExceptionHandler 接口定义如下:
使用@FunctionalInterface表示只有一个抽象方法的接口。
在线程发生异常时,jvm会调用Thread类中的dispatchUncaughtException方法
该方法调用了getUncaughtExceptionHandler()获取UncaughtExceptionHandler异常处理器的实例对象
通过源码我们可以看出,当线程发生异常时,jvm会调用Thread类中的dispatchUncaughtException方法,会判断当前线程是否设置了getUncaughtExceptionHandler,如果设置了就执行自己的uncaughtException方法,否则就使用所在ThreadGroup的uncaughtException方法。ThreadGroup 异常处理源码如下:
public void uncaughtException(Thread t, Throwable e) {
//t 发生异常的线程 e 异常信息
//判断parent(父级ThreadGroup)是否为null
if (parent != null) {
//调用父级ThreadGroup的uncaughtException方法
parent.uncaughtException(t, e);
} else {
//如果没有父级,则尝试获取全局的UncaughtExceptionHandler
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
//如果设置了全局的异常处理器,则调用全局的异常处理器的uncaughtException
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
//否则控制台输出错误信息
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
由ThreadGroup源码可以看出,这个方法其实啥也没做,不断得去找上级,没有上级就调用全局异常处理器,再没有就直接控制台打印了。因此,如果真要用ThreadGroup来获取异常,我们就需要继承ThreadGroup来重写这个方法了。
在这里,我们提到了全局异常处理器,全局异常处理器是在Thread类中定义的
现在我们编写代码来验证是否就是这样一个流程:
1、设置全局异常处理器
public static void main(String[] args) {
//设置接口回调
//设置全局的异常处理器
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(t.getName()+" occur exception");
e.printStackTrace();
});
final Thread thread = new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
//unchecked异常
System.out.println(1/0);
},"Test-Thread");
thread.start();
}
运行结果:
2、为线程添加自定义异常处理器:
//设置接口回调
//设置全局的异常处理器
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(t.getName()+" occur exception");
e.printStackTrace();
});
final Thread thread = new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
//unchecked异常
System.out.println(1/0);
},"Test-Thread");
//为某个线程设计异常处理器
thread.setUncaughtExceptionHandler((t,e)-> {
System.out.println(t.getName()+"发生异常");
e.printStackTrace();
});
thread.start();
}
运行结果:
自定义异常处理器中获取异常信息,而不是在全局异常处理器中。
3、将线程加入到group,在ThreadGroup中处理异常信息:
//继承ThreadGroup,重写uncaughtException处理异常信息
public class MyThreadGroup extends ThreadGroup {
public MyThreadGroup(String name) {
super(name);
}
public MyThreadGroup(ThreadGroup parent, String name) {
super(parent, name);
}
public void uncaughtException(Thread thread, Throwable exception){
System.out.println("ThreadGroup 捕获异常信息");
exception.printStackTrace();
}
}
public static void main(String[] args) {
ThreadGroup group1 = new MyThreadGroup("T1");
//设置全局的异常处理器
Thread.setDefaultUncaughtExceptionHandler((t,e)->{
System.out.println(t.getName()+" occur exception");
e.printStackTrace();
});
//创建线程并指定其ThreadGroup为group1
final Thread thread = new Thread(group1,()->{
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(1/0);
},"Test-Thread");
//设置线程自定义的异常处理器
//thread.setUncaughtExceptionHandler((t,e)-> {
// System.out.println(t.getName()+"发生异常");
// e.printStackTrace();
//});
thread.start();
}
注意,上面的代码我们注释掉了线程设置自定义处理器的部分,运行结果如下:
在ThreaGroup中捕获了异常信息,而不是使用全局的
解开注释,添加线程设置自定义处理器的部分,结果如下:
在自定义的异常处理器中捕获异常,而不是在ThraedGroup和全局异常处理器当中。
结论:
获取线程未捕获的异常有三种方式:
1、线程设置自定义的UncaughtExceptionHandler
2、设置全局的UncaughtExceptionHandler
3、在线程所属的ThreadGroup中捕获异常
通过上面的方式,线程发生异常时,派生它的线程就能获取到它的异常信息,知道是哪个子线程发生了错误。并且这三种方式有不同的优先级:自定义>ThreadGroup>全局