背景
项目中存在一些定时任务来更新数据库表,借助了线程池提供的一些能力,线上环境偶尔会出现网络波动导致服务实例无法连上数据库,只要出现了这种情况,就会导致数据不会再被更新,通过一些命令发现更新数据库的线程池中的所有线程都处于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状态。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。