Java 线程池内部任务出异常后,如何知道是哪个线程出了异常?
默认情况下,线程池不会直接报告哪个线程发生了异常,但是可以采取以下几种方法:
1)自定义线程池的 ThreadFactory:
- 通过自定义 ThreadFactory,为每个线程设置一个异常处理器(UncaughtExceptionHandler),在其中记录发生异常的线程信息。
2)使用 Future:
- 提交任务时使用 submit() 方法,而不是 execute(),这样可以通过 Future 对象捕获并检查任务的执行结果和异常。
3)任务内部手动捕获异常并记录:
- 在任务的 run() 方法内部,使用 try-catch 结构捕获异常,并记录或处理异常,同时记录线程信息。
方案落地
使用 ThreadFactory 和 UncaughtExceptionHandler
通过自定义 ThreadFactory,为每个线程设置一个 UncaughtExceptionHandler,记录异常信息。
public class CustomThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("Thread " + t.getName() + " threw exception: " + e);
});
return thread;
}
}
使用 Future 捕获异常
通过 submit() 提交任务,使用 Future 获取任务执行结果,如果任务抛出异常,可以通过 Future.get() 捕获并处理。
import java.util.concurrent.*;
public class ThreadPoolWithFuture {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<?> future = executor.submit(() -> {
throw new RuntimeException("Exception in thread");
});
try {
future.get(); // 可以获取报错
} catch (InterruptedException | ExecutionException e) {
System.out.println("Task threw exception: " + e.getCause());
}
executor.shutdown();
}
}
如果任务抛出异常,get() 方法会抛出 ExecutionException,其中包含任务的异常信息。
任务内部捕获异常并记录
在任务的 run() 方法内部手动捕获异常,并记录异常及线程信息。
public class TaskWithExceptionHandling implements Runnable {
@Override
public void run() {
try {
// do sth
throw new RuntimeException("Exception in task");
} catch (Exception e) {
System.out.println("手动打印:Exception caught in thread " + Thread.currentThread().getName() + ": " + e);
}
}
}
如果线程池中的线程在执行任务的时候,抛异常了,会怎么样?
一共有两种情况:
- 如果使用 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 住了,没有继续往上抛,所以没有触发移除线程和新建线程的操作。





浙公网安备 33010602011771号