乐趣区

任务异常导致线程池中的线程变为waiting状态

背景
项目中存在一些定时任务来更新数据库表,借助了线程池提供的一些能力,线上环境偶尔会出现网络波动导致服务实例无法连上数据库,只要出现了这种情况,就会导致数据不会再被更新,通过一些命令发现更新数据库的线程池中的所有线程都处于 waiting 状态。通过搜索引擎了解到以下观点:提交到线程池的任务如果抛出异常会导致线程挂掉,遂将提交到线程池的任务中可能出现的异常进行了处理,确实解决了问题。同时也留下了一个疑问:为什么任务抛出的异常会导致线程处于 waiting 状态?
本篇文章的关注点主要集中在 ScheduledThreadPoolExecutor.scheduleWithFixedDelay(..) 这个方法上,对线程池的一些原理性的内容以及相关的术语不做过多描述。
执行流程
scheduleWithFixedDelay(..) 的大体运行过程(注,ScheduledThreadPoolExecutor 类中还包含了 execute,submit,schedule 等方法,这些方法的逻辑基本是一致的):1、首先对提交的任务(Runnable 实例)进行一些包装,生成一个 ScheduledFutureTask:2、进入 delayedExecute(..) 方法,将生成的 ScheduledFutureTask 放到线程池的任务队列(注:BlockingQueue)中;3、进入 ensurePrestart() 方法,创建 Worker 实例开始处理线程:4、最后就是 addWorker 方法了,此方法主要关注以下部分:到这里,线程池中已经创建了线程,并且开始执行了。接下来就看看 Worker 线程是如何执行提交到线程池中的任务的。5、上一步中,可以看到 Worker 中持有的线程已经开始运行了,而 Worker 中的线程是这么创建的:所以,Worker 中的线程 start 之后,则开始执行 Worker 中的 run() 方法(会进入到 runWorker(..) 方法)6、上面的第 1、2 步中,会把构造的 ScheduledFutureTask 实例放到任务队列中,这里会再从任务队列中取出该实例(图中的 while 循环条件),然后再去调用该实例的 run() 方法:getTask() 方法:7、ScheduledThreadPoolExecutor.ScheduledFutureTask#run() 方法:这里的 outerTask 就是第 1 步中的 outerTask,其实就是要执行的任务本身。到了这里给出一个小结:对于周期性执行的任务,如果该任务执行失败,则后续其不会再被执行。为了内容的完整性,下面给出上图中两个方法的流程:
到此,任务异常导致线程 waiting 的原因就明了了:由于任务执行过程中抛出了异常,会造成 ScheduledFutureTask 不会再将自身放入到任务队列(BlockingQueue)中,即执行完之后,任务队列变成了一个空队列,而线程池中的 Worker 线程会以阻塞的方式从任务队列中去取任务 (第 6 步),当队列为空时,会导致所有的线程都被阻塞而进入 waiting 状态。

退出移动版