文章目录
一、线程池异常处理流程
1.1 当执行方式是 execute 时,可以看到堆栈异常的输出
原因:ThreadPoolExecutor#runWorker 方法中,task#run,即执行我们的方法,如果异常的话会throw x; 所以可以看到异常
1.2 当执行方式是 submit 时,堆栈异常没有输出。但是调用 Future#get 方法时,可以捕获到异常
原因:ThreadPoolExecutor#runWorker 方法中,task#run,其实还会继续执行 FutureTask#run 方法,再在此方法中 c#call 调用我们的方法,
如果报错时 setException,并没有抛出异常。当我们去 get 时,会将异常抛出
1.3 不会影响线程池里面其他线程的正常执行
1.4 线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。当线程异常,会调用 ThreadPoolExecutor#runWorker 方法最后面的 finally 中的processWorkerExit,会将此线程 remove,并重新 addworker 一个线程
二、源码执行流程
2.1 execute 执行流程
- 开始执行任务,新增或者获取一个线程去执行任务(比如刚开始是新增 coreThread去执行任务)。执行到 task#run 时会去执行提交的任务,如果任务执行失败,或 throw x 抛出异常
- 之后会到 finally 中的 afterExecute 扩展方法,我们可以扩展该方法对异常做些什么
- 之后因为线程执行异常会跳出 runWorker 的外层循环,进入到 processWorkerExit 方法,此方法会将执行任务失败的线程删除,并新增一个线程
- 之后会到 ThreadGroup#uncaughtException 方法,进行异常处理
- 如果没有通过 setUncaughtExceptionHandler 方法设置默认的 UncaughtExceptionHandler,就会在 uncaughtException 方法中打印出异常信息
2.2 submit 执行流程
- 将传进来的任务封装成 FutureTask,同样走 execute 的方法调用,然后直接返回 FutureTask
- 开始执行任务,新增或者获取一个线程去执行任务(比如刚开始是新增coreThread去执行任务)
- 执行到 task#run 时,因为是 FutureTask,所以会去调用 FutureTask#run
- 在 FutureTask#run 中,c#call 执行提交的任务。如果抛出异常,并不会 throw x,而是执行 setException 方法保存异常
- 当我们阻塞获取 submit 方法结果时(通过 FutureTask#get),才会将异常信息抛出。当然因为runWorker没有抛出异常,所以并不会删除线程
三、线程异常捕获方法
3.1 在 Runnable#run 方法中捕获代码异常
private static class ExecuteTask implements Runnable {
private Integer count = 0;
@Override
public String run() {
while (true) {
count++;
LOGGER.info("ExecuteTask count: {}", count);
try {
int i = 1 / 0;
} catch (Exception e) {
LOGGER.info("exception occur, message:{}, stackTrace:{}", e.getMessage(), e.getStackTrace());
}
}
}
}
3.2 在 Callable#call 方法中捕获异常
private static class SubmitTask implements Callable {
private Integer count = 0;
@Override
public String call() {
while (true) {
count++;
LOGGER.info("SubmitTask count: {}", count);
try {
int i = 1 / 0;
} catch (Exception e) {
LOGGER.info("exception occur, message:{}, stackTrace:{}", e.getMessage(), e.getStackTrace());
}
}
}
}
3.3 重写 ThreadPoolExecutor#afterExecute
/**
* Method invoked upon completion of execution of the given Runnable.
* This method is invoked by the thread that executed the task. If
* non-null, the Throwable is the uncaught {@code RuntimeException}
* or {@code Error} that caused execution to terminate abruptly.
*
* <p>This implementation does nothing, but may be customized in
* subclasses. Note: To properly nest multiple overridings, subclasses
* should generally invoke {@code super.afterExecute} at the
* beginning of this method.
*
* <p><b>Note:</b> When actions are enclosed in tasks (such as
* {@link FutureTask}) either explicitly or via methods such as
* {@code submit}, these task objects catch and maintain
* computational exceptions, and so they do not cause abrupt
* termination, and the internal exceptions are <em>not</em>
* passed to this method. If you would like to trap both kinds of
* failures in this method, you can further probe for such cases,
* as in this sample subclass that prints either the direct cause
* or the underlying exception if a task has been aborted:
*
* <pre> {@code
* class ExtendedExecutor extends ThreadPoolExecutor {
* // ...
* protected void afterExecute(Runnable r, Throwable t) {
* super.afterExecute(r, t);
* if (t == null && r instanceof Future<?>) {
* try {
* Object result = ((Future<?>) r).get();
* } catch (CancellationException ce) {
* t = ce;
* } catch (ExecutionException ee) {
* t = ee.getCause();
* } catch (InterruptedException ie) {
* Thread.currentThread().interrupt(); // ignore/reset
* }
* }
* if (t != null)
* System.out.println(t);
* }
* }}</pre>
*
* @param r the runnable that has completed
* @param t the exception that caused termination, or null if
* execution completed normally
*/
protected void afterExecute(Runnable r, Throwable t) { }