Jetty 9.2.9 QueuedThreadPool存在空闲线程时为何仍拒绝新连接
Jetty 9.2.9 低负载下异常拒绝连接问题梳理
基础配置
生产环境部署的Jetty版本为9.2.9.v20150224,线程池初始化代码如下:
new Server(new QueuedThreadPool(200, 5, 30000, new ArrayBlockingQueue<Runnable>(128)));
对应参数取值:
- maxThreads = 200
- minThreads = 5
- idleTimeout = 30000ms
- 任务队列:初始容量128的
ArrayBlockingQueue<Runnable>
问题现象
- 压测验证:应用可稳定承载200请求/秒的流量
- 异常反馈:即使系统负载极低,仍有客户端反馈新连接被拒绝
- 观测状态:运行日志中记录线程池状态为
STARTED,5<=18<=200,i=4,q=128,按字面理解当前仅18个活跃工作线程,远低于200的最大线程上限,不存在线程资源耗尽的情况 - 错误日志片段:
2021-01-08 00:20:47,813 WARN [qtp1720926658-14444] QueuedThreadPool : QueuedThreadPool[qtp1720926658]@669341c2{STARTED,5<=18<=200,i=4,q=128}[ReservedThreadExecutor@58a5954d{s=3/4,p=1}] rejected org.eclipse.jetty.io.ManagedSelector$DestroyEndPoint@263c181 2021-01-08 00:20:47,813 WARN [qtp1720926658-14444] QueuedThreadPool : QueuedThreadPool[qtp1720926658]@669341c2{STARTED,5<=18<=200,i=3,q=128}[ReservedThreadExecutor@58a5954d{s=3/4,p=1}] rejected org.eclipse.jetty.io.ManagedSelector$DestroyEndPoint@431081c5 2021-01-08 00:20:47,813 WARN [qtp1720926658-14445] QueuedThreadPool : QueuedThreadPool[qtp1720926658]@669341c2{STARTED,5<=18<=200,i=2,q=128}[ReservedThreadExecutor@58a5954d{s=3/4,p=1}] rejected org.eclipse.jetty.io.ManagedSelector$DestroyEndPoint@3866d8cd 2021-01-08 00:20:47,813 WARN [qtp1720926658-14442] QueuedThreadPool : QueuedThreadPool[qtp1720926658]@669341c2{STARTED,5<=18<=200,i=6,q=126}[ReservedThreadExecutor@58a5954d{s=1/4,p=2}] rejected CEP:SocketChannelEndPoint@4aa81d65{/10.0.1.29:53143<->/10.0.1.28:7777,OPEN,fill=FI,flush=-,to=16442/30000}{io=1/0,kio=1,kro=1}->HttpConnection@32cdb0b1[p=HttpParser{s=START,0 of -1},g=HttpGenerator@22c521c0{s=START}]->HttpChannelOverHttp@e02e50f{r=1,c=false,a=IDLE,uri=null,age=0}:runFillable:BLOCKING 2021-01-08 00:20:47,829 WARN [qtp1720926658-14442] EatWhatYouKill : java.util.concurrent.RejectedExecutionException: CEP:SocketChannelEndPoint@4aa81d65{/10.0.1.29:53143<->/10.0.1.28:7777,OPEN,fill=FI,flush=-,to=16442/30000}{io=1/0,kio=1,kro=1}->HttpConnection@32cdb0b1[p=HttpParser{s=START,0 of -1},g=HttpGenerator@22c521c0{s=START}]->HttpChannelOverHttp@e02e50f{r=1,c=false,a=IDLE,uri=null,age=0}:runFillable:BLOCKING at org.eclipse.jetty.util.thread.QueuedThreadPool.execute(QueuedThreadPool.java:440) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.execute(EatWhatYouKill.java:370) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:305) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126) at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:762) at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:680) at java.lang.Thread.run(Thread.java:748)
根因解析
QueuedThreadPool打印的状态串有固定含义,格式为:
运行状态,最小线程数<=当前活跃线程数<=最大线程数,i=空闲线程数,q=队列当前积压任务数
- 日志中反复出现的
q=128,和初始化时设置的队列容量完全一致,说明有界队列已经完全被任务占满,无法再接收新任务。 - 异常的核心来源是9.2.9版本QueuedThreadPool的设计缺陷:该版本QueuedThreadPool默认内置无界队列,对应的任务提交逻辑完全适配无界队列特性——只要当前线程数没到maxThreads,就不会因为队列满触发拒绝。当手动传入自定义的有界
ArrayBlockingQueue时,线程池不会主动探测队列的实际容量,始终默认队列是无界的。 - 这种设计缺陷下,当自定义的有界队列真的被任务填满时,QueuedThreadPool会直接触发拒绝逻辑,完全跳过“判断当前线程数是否低于maxThreads、需要扩容”的分支,最终出现“活跃线程远低于上限、任务直接被拒绝”的反常现象。
- 日志中被拒绝的任务包含两类:Jetty内部的连接销毁任务(
ManagedSelector$DestroyEndPoint)、连接可读事件处理任务(runFillable:BLOCKING),这类内部处理任务的优先级低于用户请求业务,会最先被填满的队列挡住,被触发拒绝逻辑。
修复方案
- 方案一:移除手动传入的有界队列,直接使用QueuedThreadPool默认的无界队列初始化逻辑,让线程池完全按照原生流程运行,不会出现容量识别偏差:
对应代码:new QueuedThreadPool(200, 5, 30000) - 方案二:如果业务确实需要限制队列容量,不要手动实例化
ArrayBlockingQueue传入构造函数,改为调用QueuedThreadPool内置的setQueueCapacity()方法设置队列大小。该方法会在线程池内部正确初始化有界队列,让扩容逻辑可以准确识别队列边界,匹配有界队列的提交流程:
示例代码:QueuedThreadPool threadPool = new QueuedThreadPool(200, 5, 30000); threadPool.setQueueCapacity(128); - 长期优化建议:9.2.9是2015年发布的老旧版本,上述自定义队列兼容问题是该版本的已知缺陷,后续9.2.x系列的补丁版本已修复该类问题,条件允许可升级到同系列最新稳定补丁版本。
内容的提问来源于stack exchange,提问作者Ashish
相关产品推荐
相关产品推荐

