【线程】Thread.UncaughtExceptionHandler 实战与剖析 (十八)
程序员文章站
2024-03-20 17:10:58
...
我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
文章目录
- 一、前言
- 二、实战 demo
- 2.1 实战一:测试 Exception 逃逸
- 2.2 实战二:自定义 UncaughtExceptionHandler
- 2.3 实战三:全局定义 UncaughtExceptionHandler
- 2.4 实战四:全局 Handler 与自定义 Handler 并存,听谁的?
- 三、代码剖析
- 3.1 uncaughtExceptionHandler 与 defaultUncaughtExceptionHandler
- 3.2 setUncaughtExceptionHandler 方法
- 3.3 setDefaultUncaughtExceptionHandler 方法
- 3.4 dispatchUncaughtException 方法
- 3.5 getUncaughtExceptionHandler 方法
- 3.6 getDefaultUncaughtExceptionHandler 方法
- 四、番外篇
一、前言
1.1 架构
- java.lang.Thread.UncaughtExceptionHandler
1.2 Class 架构
public class Thread implements Runnable {
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
}
1.3 能干嘛?
- 捕获子线程抛出的异常
二、实战 demo
2.1 实战一:测试 Exception 逃逸
- 代码
public class _01_TestMultiThreadException {
// 现象:控制台打印出异常信息,并运行一段时间后才停止
public static void main(String[] args) {
// 就算把线程的执行语句放到 try-catch 块中也无济于事
try {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(() -> {
throw new RuntimeException("自定义的一个RuntimeException");
});
exec.shutdown();
} catch (Exception e) {
System.out.println("Exception has been handled!");
}
}
}
-
解析:上方的 try-catch 压根没法捕获到子线程抛出的 Exception
-
结论:
- 多线程运行不能按照顺序执行过程中捕获异常的方式来处理异常, 异常会被直接抛出到控制台。
- 由于线程的本质,使得你不能捕获从线程中逃逸的异常。一旦异常逃逸出任务的run方法,它就会向外传播到控制台,除非你采用特殊的形式捕获这种异常。
2.2 实战二:自定义 UncaughtExceptionHandler
- 步骤一:定义“异常处理器” UncaughtExceptionHandler
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
/*
Thread.UncaughtExceptionHandle.uncaughtException()
会在线程因未捕获的异常而临近死亡时被调用
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
// 这里可以写 if else 处理各种各样的异常
if(e instanceof RuntimeException){
System.out.println("### MyUncaughtExceptionHandler catch " + e);
}
}
}
- 步骤二:定义线程工厂 ThreadFactory
- 工厂就是用来产生线程的,并给生成的线程绑定一个异常处理器。
class MyHandlerThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
System.out.println("MyHandlerThreadFactory creating new Thread ==== Begin ====");
Thread thread = new Thread(r);
System.out.println("MyHandlerThreadFactory created " + thread);
// !!! 设定线程工程的异常处理器
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("eh = " + thread.getUncaughtExceptionHandler());
System.out.println("MyHandlerThreadFactory creating new Thread ==== End ====");
return thread;
}
}
- 步骤三:定义一个会抛出unchecked异常的线程类
class ExceptionThreadDemo implements Runnable {
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("run by -> " + t);
System.out.println("eh -> " + t.getUncaughtExceptionHandler());
throw new RuntimeException("这是自定义的 RuntimeException ... ");
}
}
- 步骤四:使用线程工厂创建线程池,并调用其 execute 方法
public static void test01(){
ExecutorService exec = Executors.newCachedThreadPool(new MyHandlerThreadFactory());
exec.execute(new ExceptionThreadDemo());
exec.shutdown();
}
- 输出:
MyHandlerThreadFactory creating new Thread ==== Begin ====
MyHandlerThreadFactory created Thread[Thread-0,5,main]
eh = indi.swaaa@qq.comb81eda8
MyHandlerThreadFactory creating new Thread ==== End ====
run by -> Thread[Thread-0,5,main]
eh -> indi.swaaa@qq.comb81eda8
### MyUncaughtExceptionHandler catch java.lang.RuntimeException: 这是自定义的 RuntimeException ...
- 结论: Exception 被抓住了。
=== 点击查看top目录 ===
2.3 实战三:全局定义 UncaughtExceptionHandler
- 如果你知道将要在代码中每个地方使用相同的异常处理器,那么更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获处理器。
- 实战三相对实战二,去掉了为每个线程专门绑定处理器的 ThreadFactory
public static void test02(){
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ExceptionThreadDemo());
exec.shutdown();
}
输出:
run by -> Thread[pool-1-thread-1,5,main]
eh -> java.lang.ThreadGroup[name=main,maxpri=10]
### MyUncaughtExceptionHandler catch java.lang.RuntimeException: 这是自定义的 RuntimeException ...
2.4 实战四:全局 Handler 与自定义 Handler 并存,听谁的?
- 代码
public static void test03(){
Thread.setDefaultUncaughtExceptionHandler((t,e) -> {
if(e instanceof RuntimeException){
System.out.println("### default catch " + e);
}
});
ExecutorService exec2 = Executors.newCachedThreadPool(new MyHandlerThreadFactory());
exec2.execute(new ExceptionThreadDemo());
exec2.shutdown();
}
- 输出:
MyHandlerThreadFactory creating new Thread ==== Begin ====
MyHandlerThreadFactory created Thread[Thread-0,5,main]
eh = indi.swaaa@qq.com1a93a7ca
MyHandlerThreadFactory creating new Thread ==== End ====
run by -> Thread[Thread-0,5,main]
eh -> indi.swaaa@qq.com1a93a7ca
### MyUncaughtExceptionHandler catch java.lang.RuntimeException: 这是自定义的 RuntimeException ...
- 结论:
- 没有"### default catch "的输出,说明没有进入 default 板块,而是走自定义的那一个
- 这个默认的处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用。
三、代码剖析
3.1 uncaughtExceptionHandler 与 defaultUncaughtExceptionHandler
public class Thread implements Runnable {
...
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
...
}
- uncaughtExceptionHandler 对单个 thread 设置的
- defaultUncaughtExceptionHandler 默认的
3.2 setUncaughtExceptionHandler 方法
- java.lang.Thread#setUncaughtExceptionHandler 方法
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
- 设置变量 uncaughtExceptionHandler ,使其不为 null
3.3 setDefaultUncaughtExceptionHandler 方法
- java.lang.Thread#setDefaultUncaughtExceptionHandler 方法
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}
- 设置默认变量 setDefaultUncaughtExceptionHandler ,使其不为 null
3.4 dispatchUncaughtException 方法
- java.lang.Thread#dispatchUncaughtException
/**
* 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);
}
dispatchUncaughtException 方法由 JVM 控制,也就是子线程抛出异常之后,调用的Exception处理方法。
=== 点击查看top目录 ===
3.5 getUncaughtExceptionHandler 方法
- java.lang.Thread#getUncaughtExceptionHandler
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
- 该方法用于获取真正处理异常的 Handler 方法
- uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; 若 thread 定制了 Handler ,那么走 uncaughtExceptionHandler, 如果没有指定,就返回线程组 group。
- 注意:ThreadGroup 实现了 Thread.UncaughtExceptionHandler
public class ThreadGroup implements Thread.UncaughtExceptionHandler {}
3.5.1 为什么== 实战4 ==定制的优先与默认的?
- 因为定制了 handler,所以在 getUncaughtExceptionHandler 的时候,走了定制的。
3.5.2 若只有 default,流程如何走?
- getUncaughtExceptionHandler() 方法,返回了 group
3.5.3 uncaughtException 方法
- java.lang.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);
}
}
}
- 内部调用了 Thread.getDefaultUncaughtExceptionHandler()
- 递归调用,从老一辈开始
=== 点击查看top目录 ===
3.6 getDefaultUncaughtExceptionHandler 方法
- java.lang.Thread#getDefaultUncaughtExceptionHandler
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
四、番外篇
推荐阅读