线程池执行的用户任务抛出异常会怎样?
共 3056字,需浏览 7分钟
·
2022-11-22 10:48
点击关注公众号,Java干货及时送达👇
来源:blog.csdn.net/qq_26824159/article/details/126767372
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
会将用户任务包装为ScheduledFutureTask
,ScheduledFutureTask
是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
。
2. SpringBoot + Flyway,自动化实现数据库版本控制
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦