一次触发线程池拒绝策略问题的排查
这个问题的原因是团队中的同事排查出来的,本人觉得这个问题很经典,所以记录一下
线上偶尔会出现线程池拒绝策略触发的告警
每次出现告警都会查看数据库、JVM等监控,系统都是比较平稳的,没有出现请求暴涨的情况,所以认为大概率是线程池配置的问题
分析过程
线程池参数有很多,但最核心的是三个corePoolSize、maximumPoolSize、workQueuequeueCapacity,三者的关系如下
系统通过JDK原生线程池ThreadPoolTaskExecutor设置线程池参数,细细看了这个类的代码,corePoolSize、maxPoolSize、keepAliveSeconds等参数都是正常的设置(set),只有queueCapacity参数做了一些判断,代码如下
如果阻塞队列容量大于0,阻塞队列使用LinkedBlockingQueue,等于0,使用SynchronousQueue
于是做个测试,看看是不是这里导致的
测试代码如下,如果触发了线程池拒绝策略,打印出来
理论上核心线程数有10个,一组执行完,另一组还可以使用核心线程执行,是不需要触发非核心线程的,结果出乎我的意料
queueCapacity等于1的情况
queueCapacity等于0的情况
显然,使用LinkedBlockingQueue,线程复用率不高,导致触发了线程池拒绝策略
那么问题来了,为什么LinkedBlockingQueue线程复用率不高,google搜索了一下
千篇一律都是这种类型的文章,只有结论,没有说为什么,所以只能去看源码
线程池源码
线程池提交任务的代码如下
这段代码跟文章开头那张图是完美对应的,核心代码在于 workQueue.offer(command)
,因为入队失败,才会 addWorker(command, false)
创建非核心线程
所以为什么SynchronousQueue会入队成功,LinkedBlockingQueue会入队失败,只能看两个队列的源码,比较一下,下面简单说说两者的源码
SynchronousQueue源码
SynchronousQueue代码还是蛮复杂的,有公平非公平两种模式,默认是非公平,如下图
- SynchronousQueue每个节点有三个状态,分别是未匹配的消费者、未匹配的生产者、正在匹配另一个生产者或消费者
- 当队列为空时,生产者或者消费者会直接进入栈中,自旋或者阻塞等待生产者或者消费者匹配
- 当栈中有生产者,消费者进入会去匹配该生产者,或者当栈中有消费者,生产者进入会进行匹配,匹配完成一起出栈
- 当栈中有生产者与消费者正在匹配,又进来了一个生产者或者消费者线程,它会协助两者匹配
看上是不是很懵,其实我一开始也很懵,juc包的代码是很难理解的,下面是详细的代码
LinkedBlockingQueue 源码解析
我的一点猜想
- LinkedBlockingQueue存取节点使用锁进行了互斥,性能没有SynchronousQueue好,所以同样的数据SynchronousQueue处理起来更快
- SynchronousQueue支持多线程协作栈中数据,可以充分利用多线程的好处,不像LinkedBlockingQueue,队列到达上限,直接返回false,任务只能创建非核心线程
由于本人水平有限,无法在多线程的环境下验证自己的猜想,期待大佬指点一二