Java 线程池内部任务出异常后,如何知道是哪个线程出了异常?
Java 线程池内部任务出异常后,如何知道是哪个线程出了异常?
重点回答
默认情况下,线程池不会直接报告哪个线程发生了异常,但是可以采取以下几种方法:
1)**自定义线程池的 ThreadFactory**:
- 通过自定义
ThreadFactory,为每个线程设置一个异常处理器(UncaughtExceptionHandler),在其中记录发生异常的线程信息。
2)**使用 Future**:
- 提交任务时使用
submit()方法,而不是execute(),这样可以通过Future对象捕获并检查任务的执行结果和异常。
3)任务内部手动捕获异常并记录:
- 在任务的
run()方法内部,使用try-catch结构捕获异常,并记录或处理异常,同时记录线程信息。
扩展知识
方案落地
使用 ThreadFactory 和 UncaughtExceptionHandler
通过自定义 ThreadFactory,为每个线程设置一个 UncaughtExceptionHandler,记录异常信息。
1 |
|
使用 Future 捕获异常
通过 submit() 提交任务,使用 Future 获取任务执行结果,如果任务抛出异常,可以通过 Future.get() 捕获并处理。
1 | import java.util.concurrent.*; |
如果任务抛出异常,get() 方法会抛出 ExecutionException,其中包含任务的异常信息。
任务内部捕获异常并记录
在任务的 run() 方法内部手动捕获异常,并记录异常及线程信息。
1 | public class TaskWithExceptionHandling implements Runnable { |
如果线程池中的线程在执行任务的时候,抛异常了,会怎么样?
一共有两种情况:
- 如果使用
execute()提交任务,任务执行时抛出未捕获异常,线程会被移除,线程池会创建新线程; - 如果使用
submit()提交任务,任务执行时抛出未捕获异常,异常会封装在ExecutionException中返回,不会抛出,且不会创建新线程。
看下execute()相关源码,最终会调用 runWorker 方法:

任务执行逻辑就是 task.run() ,可以看到它被 try catch finally包裹,异常被扔到了 afterExecute 中,并且也继续被抛了出来。
而这一层外面,还有个try finally,所以异常的抛出打破了 while 循环,最终会执行 processWorkerExit 方法。
我们来看下这个方法,其实逻辑很简单,把这个线程废了,然后新建一个线程替换之。

移除了引用就等于销毁了,后续会被 GC 了。
所以如果一个任务执行一半就抛出异常,并且你没有自行处理这个异常,那么这个任务就这样戛然而止了,后面也不会有线程继续执行剩下的逻辑,所以要自行捕获和处理业务异常。
实际上 submit 最终也会调用 execute,差别就在于包了一层 RunnableFuture。
那么 submit 也调用了 execute 为什么不会创建新线程呢?
门道就在 RunnableFuture,从下面代码可以看到 RunnableFuture 的实现类 FutureTask 中的 run 方法有个setException(ex); 逻辑,就是它把异常给 catch 住了,没有继续往上抛,所以没有触发移除线程和新建线程的操作。