Java 线程池队列满了之后任务到底去哪了

举报
yd_286814013 发表于 2026/10/26 00:58:02 2026/10/26
【摘要】 线上服务出现过一次任务凭空消失,排查后发现是线程池队列满了之后没有正确配置拒绝策略。这里把整个过程和结论整理一下。线程池处理新任务的顺序是固定的:先看当前线程数有没有达到 corePoolSize,没达到就新建核心线程;达到了就尝试放进 workQueue;队列也满了才继续扩线程直到 maximumPoolSize;还满才走 handler。关键在于这个顺序。队列用的是有界队列时,比如 Ar...

线上服务出现过一次任务凭空消失,排查后发现是线程池队列满了之后没有正确配置拒绝策略。这里把整个过程和结论整理一下。

线程池处理新任务的顺序是固定的:先看当前线程数有没有达到 corePoolSize,没达到就新建核心线程;达到了就尝试放进 workQueue;队列也满了才继续扩线程直到 maximumPoolSize;还满才走 handler。

关键在于这个顺序。队列用的是有界队列时,比如 ArrayBlockingQueue 长度 200,那么在前 200 个等待任务填满之前,线程数根本不会超过 corePoolSize。也就是说 maximumPoolSize 设得再大,只要队列没满,它永远用不上。

真正坑人的是有界队列加 CallerRunsPolicy。看起来任务没丢,实际上跑在提交任务的那个线程上。如果提交方是 Web 容器的业务线程,那就变成业务线程去执行耗时任务,接口响应时间直接飙升,超时重试再来一轮,压力翻倍。

无界队列 LinkedBlockingQueue 默认长度是 Integer.MAX_VALUE,等于永远不触发扩容,也不触发拒绝策略。任务全堆在队列里,堆积到内存吃满才 OOM。这种写法在压测里不容易暴露,因为压测时间短,一到生产高峰就出事。

拒绝策略四种里只有 AbortPolicy 会抛 RejectedExecutionException,其余三种都是静默处理。默认是 AbortPolicy,被很多人改成了 DiscardPolicy,任务直接丢,日志里什么都没有,这就是任务消失的来源。

几条实践建议。一、队列必须有界,长度按可接受的延迟估算,不要用默认构造。二、corePoolSize 和 maximumPoolSize 设成一样,避免扩容判断带来的延迟抖动,需要隔离就拆成多个池。三、拒绝策略显式指定,并且在里面打日志或者上报指标,不要用 Discard 系列。四、线程池要能被监控,队列长度、活跃线程数、拒绝次数三个指标至少要有,出问题时才看得见。

最后提醒一句,很多框架内部自带线程池,比如异步 Servlet、定时任务、数据库连接池的回调线程。排查任务丢失时不要只看自己 new 的那几个,框架里那些同样会静默丢弃。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。