⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/qq_26824159/article/details/126767372 「lanicc」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

ThreadPoolExecutor.execute

源码分析

看源码可以知道,ThreadPoolExecutor中的任务都是在runWorker中执行的

通过源码可以看到

  • 1149行执行用户任务
  • 1150~1155处理捕获任务异常,并抛出
  • 抛出异常后会退出,从任务队列中拉取任务的循环
  • 然后执行1167行,worker线程退出的逻辑

看一下线程退出的逻辑

  • 如果是异常退出,参数completedAbruptly为true
  • 如果状态值比STOP小,即线程池没有停止,会重新创建一个worker线程

总结

ThreadPoolExecutor.execute 如果用户任务抛出了异常,在线程池运行的状态下,会重新创建一个worker线程。

这里就可能存在一个风险,即如果用户任务大量的抛出异常,可能会导出线程资源频繁的销毁、创建。

因此,需要用户任务应当主动对异常进行处理,而不是消极的抛给线程池。

ThreadPoolExecutor.submit

源码分析

通过 ThreadPoolExecutor.submit 提交的用户任务,会包装成一个FutureTask,返回一个Future对象。因此异常处理的逻辑和ThreadPoolExecutor.execute有些差别

看一个FutureTask.run方法

从源码可以看到,FutureTask执行用户任务,如果异常,不会对外抛出,仅是记录

但是在调用Future.get时,会抛出异常,但此时的线程不是线程池的线程了,而是用户线程,因此对线程池是友好的。

总结

ThreadPoolExecutor.submit 提交的用户任务,会包装成一个FutureTask,FutureTask执行用户任务,如果异常,不会对外抛出,仅是记录,但是在调用Future.get时,会抛出异常。

异常是在用户线程中抛出的,因此不影响线程池中的线程。

ScheduledThreadPoolExecutor.schedule

源码分析

ScheduledThreadPoolExecutor.schedule会将用户任务包装为ScheduledFutureTaskScheduledFutureTask是FutureTask的子类,看下ScheduledFutureTask的执行逻辑

ScheduledFutureTask是FutureTask的子类,所以有异常时,也不是抛出,而是记录

  • 293行对于非周期性任务,执行一次,如果有异常,不抛出,仅记录
  • 294~296对于周期性任务,执行完本次后,会设置下次执行的时间。如果本次出现异常,ScheduledFutureTask.super.runAndReset()返回结果为false(这个可以从源码看到),此时不会设置下次任务调度的时间了,因此会导致周期性任务失效的现象,并且异常信息不会抛出,也不会打印

总结

ScheduledThreadPoolExecutor.schedule提交的用户任务,如果出现异常,是不会抛出的,也不会打印。

对于非周期性任务和周期性任务,执行异常,都没有办法感知(不抛出、看不到)。

对于周期性任务,任何一次调度时,任务出现异常,都会导致后续无法调度。

因此,在使用这个线程池时,我们应当保证用户任务不会抛出异常到执行线程,避免任务调度失效,和异常排查困难等问题。

思考:ThreadPoolExecutor.execute发生异常时为什么要退出

ThreadPoolExecutor.execute出现用户任务异常时,为什么要退出当前线程,再重新创建一个线程呢?

我思考了半天,觉得原因之一可能是:

ThreadPoolExecutor的可以指定线程工厂,如果我的线程工厂是这样的

ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 处理异常的逻辑
}
});
return thread;
}
};

即在ThreadFactory创建线程时,指定了对未捕获的异常的处理器。

这种情况下,如果线程池不把发生异常的线程退出,可能会导致异常没有走到用户期望的逻辑上,因此需要将发生异常的线程退出,然后JVM调用UncaughtExceptionHandler

文章目录
  1. 1. ThreadPoolExecutor.execute
    1. 1.0.1. 源码分析
    2. 1.0.2. 总结
  • 2. ThreadPoolExecutor.submit
    1. 2.0.1. 源码分析
    2. 2.0.2. 总结
  • 3. ScheduledThreadPoolExecutor.schedule
    1. 3.0.1. 源码分析
    2. 3.0.2. 总结
  • 4. 思考:ThreadPoolExecutor.execute发生异常时为什么要退出