前言
Thread t = new Thread(() -> System.out.println(1 / 0));
t.start(); 如果我们执行上面这段代码,会在控制台上看到异常输出。通常情况下绝大多数线上应用不会将控制台作为日志输出地址,而是另有日志输出。这种情况下,上面的代码所抛出异常便会丢失。那为了将异常输出到日志中,我们会这样写代码:
hread t = new Thread(() -> {
try {
System.out.println(1 / 0);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
});
t.start();
这样我们就能异常栈输出到日志中,而不是控制台,从而避免异常的丢失。可能好多线程任务默认的异常处理机制都是相同的。比如都是将异常输出到日志文件。按照上面的写法会造成重复代码。那我们该如何解决这个问题呢?
Java线程–全局异常处理
Thread 类中有个接口 UncaughtExceptionHandler。通过实现这个接口,并调用 Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler) 方法,我们就能为一个线程设置默认的异常处理机制,避免重复的 try…catch 了。除此以外,我们还可以通过 Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler) 设置全局的默认异常处理机制。此外,ThreadGroup 也实现了 UncaughtExceptionHandler 接口,所以通过 ThreadGroup 还可以为一组线程设置默认的异常处理机制。其实,之所以代码2在执行之后我们能在控制台上看到异常,也是因为 UncaughtExceptionHandler 机制。ThreadGroup默认提供了异常处理机制如下:
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);
}
}
}
ThreadPoolExecutor处理异常
(1)在提交的任务中将异常捕获并处理,不抛给线程池。
(2)异常抛给线程池,但是我们要及时处理抛出的异常。
1、直接catch
第一种思路很简单,就是我们提交任务的时候,将所有可能的异常都Catch住,并且自己处理。
说白了就是把业务逻辑都trycatch起来。 但是这种思路的缺点就是:
所有的不同任务类型都要trycatch,增加了代码量。
不存在checkedexception的地方也需要都trycatch起来,代码丑陋。
自定义线程池,继承ThreadPoolExecutor并复写其afterExecute(Runnable r, Throwable t)方法。
实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory
继承ThreadGroup
覆盖其uncaughtException方法。(与第二种方式类似,因为ThreadGroup类本身就实现了Thread.UncaughtExceptionHandler接口)
尤其注意:上面三种方式针对的都是通过execute(xx)的方式提交任务,如果你提交任务用的是submit()方法,那么上面的三种方式都将不起作用,而应该使用下面的方式
采用Future模式
如果提交任务的时候使用的方法是submit,那么该方法将返回一个Future对象,所有的异常以及处理结果都可以通过future对象获取。 采用Future模式,将返回结果以及异常放到Future中,在Future中处理
|