Java 线程池队列满了之后任务到底去哪了
线上服务出现过一次任务凭空消失,排查后发现是线程池队列满了之后没有正确配置拒绝策略。这里把整个过程和结论整理一下。
线程池处理新任务的顺序是固定的:先看当前线程数有没有达到 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 的那几个,框架里那些同样会静默丢弃。
- 点赞
- 收藏
- 关注作者
评论(0)