深蓝黑背景品牌封面:居中大标题「线程池参数搞不懂,面试肯定挂」,副标题「核心线程、任务队列与拒绝策略」;下方原创装饰——三个工人圆点从任务队列里依次取任务执行,队列末端一个任务被画叉拒绝;顶部标注「经典面试题解析 · Java 后端」与「Day 04 · 09-22」 图 1:本文封面,点明题目与三条主线。

先说明属性:这是一篇经典题解析,题目出自本地历史面试资料(见文末来源说明),与任何公司的当前招聘无关。线程池是 Java 并发面试的高频题,但它的真正考点不是背七个参数的名字,而是能不能讲清楚一个新任务提交之后,线程池内部的决策顺序——这条流程线串起了所有参数的意义。

想象面试现场,面试官问:「ThreadPoolExecutor 的核心参数有哪些?一个任务进来,线程池怎么处理?队列满了怎么办?」

第一步,审题。

把问题拆成三层:参数各管什么;任务接收的决策流程是什么;撑不住的时候怎么收场。三层之间是因果关系——决策流程解释了为什么参数要这么设计。

第二步,基础回答:参数与决策流程。

核心参数记住五个就够:corePoolSize(核心线程数,常驻)、maximumPoolSize(最大线程数,上限)、keepAliveTime(超出核心数的线程空闲多久回收)、workQueue(任务队列)、handler(拒绝策略)。JDK 官方文档把任务接收流程写得非常明确,依次三道判断:运行线程数少于核心线程数,直接新建线程执行,哪怕有空闲线程;核心数已满,优先进队列排队而不是加人;队列也满了,线程数还没到最大值,才新建临时线程;三者都不行,触发拒绝策略。

这个顺序有个反直觉的重点:先排队、后加人。也就是说,maximumPoolSize 只在队列装满之后才有机会生效——这直接决定了队列选型的分量。

第三步,拒绝策略与队列选型。

官方文档给了四种预置拒绝策略:AbortPolicy 直接抛 RejectedExecutionException,是默认值,异常能逼调用方感知过载;CallerRunsPolicy 让提交任务的线程自己执行,官方称这是一种简单的反馈控制机制——提交者被占用,任务来得自然变慢,等于天然反压;DiscardPolicy 静默丢弃,只适合结果无人关心的任务;DiscardOldestPolicy 丢掉队头最老的任务再重试,官方文档直言这种策略几乎不可接受,用它至少要记得给等待方补异常或日志。

队列同样三种思路:直接交接的 SynchronousQueue,不存任务,适合配近乎无界的最大线程数;无界队列如 LinkedBlockingQueue,任务无限排队——官方文档点破后果:线程数永远不会超过核心数,maximumPoolSize 形同虚设,代价是任务无限积压、内存被拖垮;有界队列如 ArrayBlockingQueue 是工程上的正解,队列容量与线程上限互相制衡。

决策流程图:新任务提交后依次经过三道判断——核心线程未满则创建核心线程执行;否则任务队列未满则入队排队;否则线程数未达最大值则创建临时线程执行;三者都不行才触发拒绝策略。右侧列出四种拒绝策略:Abort 抛异常(默认)、CallerRuns 由提交者自己执行形成天然反压、Discard 静默丢弃、DiscardOldest 丢弃最老任务;并给出配置经验——CPU 密集约核数加一、IO 密集约核数两倍起、不要用无界队列。底部总结:先核心、再排队、后加人、都不行才拒绝;参数语义以当前 JDK 文档为准 图 2:任务接收的三道判断、四种拒绝策略与配置经验。

第四步,怎么按场景配置。

经验口径:CPU 密集型任务,线程数约等于核数加一——多了只是增加上下文切换;IO 密集型任务线程大部分时间在等,从核数两倍起配,再按实测调整。比公式更重要的三条纪律:一,不要用无界队列,它把过载藏成内存事故;二,拒绝策略默认 Abort 往往是对的,让过载显式暴露;三,用好文档里提到的监控接口(活跃线程数、累计完成任务数),线程池是要被观测的,不是配完就忘的。

还要提一句工厂方法的坑。JDK 文档建议常见场景优先用 Executors 的工厂方法,但 newFixedThreadPool 内部配的是无界队列,newCachedThreadPool 的最大线程数近乎无界——两个「方便」的默认池各自埋着内存耗尽和线程爆炸的雷。生产环境更稳妥的做法是显式 new ThreadPoolExecutor,把核心数、上限、有界队列、拒绝策略逐一写明,配置即评审。

易错点清单。

一,以为任务多了就会加线程——队列不满就不会加,「先排队后加人」是这道题的灵魂。二,用无界队列配 Executors 工厂方法的默认池,把内存风险埋进生产。三,以为 shutdown 是立刻停——官方文档说明它是有序关闭:已提交的任务继续执行完,只是不再接新任务;要立刻停是 shutdownNow,且不保证任务响应中断。四,背参数不讲流程,被一句「队列满了会怎样」问倒。

回到面试现场,建议的答题节奏:先画决策流程(核心→队列→临时线程→拒绝),再讲四种拒绝策略的取舍,然后落到 CPU/IO 场景的配置口径,最后补一句监控与无界队列的坑。流程讲得顺,参数不用背也全在里面。

批量推理、并发采集、异步任务队列都靠线程池承载,参数拍脑袋是线上雪崩的常见起点。你配置线程池时踩过最大的坑是什么?欢迎分享。

参考资料

  • ThreadPoolExecutor(JDK 21 官方文档:核心/最大线程数与任务接收顺序、三种队列策略及其后果、四种预置拒绝策略、keepAliveTime 回收规则、shutdown 与 shutdownNow 语义、监控统计方法) — https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html