软件技术 · 知识点深化 · 并发编程
线程池原理与七大参数:从核心线程到拒绝策略
线程池不是帮你"多开几个线程"那么简单。它是一个任务排队 + 线程复用的调度器:核心线程常驻、新任务进队列、队列满了才扩容到最大线程数、实在扛不住就拒绝。这一页把七个参数、四种拒绝策略、执行流程一条线讲透,面试时能徒手画出来。
① 怎么学(4 步走,约 70 分钟)
先建立直觉再抠细节,按这四步走最稳:
1看图建立直觉(10 分钟)
读②③:先在脑子里画出本课核心结构图。
2记完整体系(15 分钟)
读④:把对比表和公式看懂,不要急着背。
3跟例题走一遍(20 分钟)
精读⑤:看三个例题怎么用知识点解题。
4刷题纠错(剩余时间)
做⑦⑩,错题回⑥诊断。
本课小目标学完你要能:① 说出线程池七个参数含义;② 画出任务从提交到执行的完整流程;③ 说出四种拒绝策略及适用场景;④ 解释为什么不推荐 Executors 工厂方法。
② 一图看懂:线程池执行流程全景
读法:中心是本课主题,四条分支展开核心维度,下方是典型应用与高频易错点。
③ 本质直觉:线程池就是"共享办公室 + 取号机"
把线程池想成一个共享办公室:来了个任务(顾客),前台先看有没有空闲的核心员工(核心线程),有就直接接待。核心员工都在忙,就让任务去候客椅上排队(阻塞队列)。椅子也坐满了,临时招外包员工(非核心线程)直到人数上限(maxPoolSize)。连外包都招满了,就只能拒收(拒绝策略)。
为什么不用 new Thread()?线程创建销毁要花 OS 资源,每秒几万个请求就创建几万个线程,直接把机器打挂。线程池让线程复用:一个线程干完一个任务后不退出,继续从队列拿下一个。
关键顺序:核心线程→队列→最大线程→拒绝。很多人以为队列满了才扩容,对;但有人以为先扩到最大再排队,错——永远是先排队再扩容。
为什么先排队再扩容这是 Doug Lea 的设计哲学:队列能缓冲就缓冲,避免频繁创建销毁线程;只有队列真的满了才被迫加线程。顺序搞反会导致线程数暴涨。
④ 完整知识体系
七个参数与四种拒绝策略
| 参数/策略 | 含义 | 注意点 |
| corePoolSize | 常驻核心线程数 | 默认可以allowCoreThreadTimeOut |
| maximumPoolSize | 最大线程数 | 必须≥corePoolSize |
| keepAliveTime | 空闲线程存活时间 | 只针对非核心线程 |
| workQueue | 任务缓冲队列 | 有界队列防 OOM |
| threadFactory | 线程工厂 | 给线程起名字便于排查 |
| handler | 拒绝策略 | 队列满+线程满时触发 |
| Executors.newFixed | 固定线程数 | 队列无界,可能 OOM |
执行顺序任务来了 → 核心线程满了? → 入队 → 队列满了? → 扩容到 max → 还满? → 拒绝
CPU 密集型核心数 = N+1(偶尔页缺失停顿)
IO 密集型核心数 = 2N 或更多(线程大量时间在等待)
四种拒绝策略
AbortPolicy(默认):抛 RejectedExecutionException。
CallerRunsPolicy:谁提交谁执行,让提交线程自己跑,反压。
DiscardPolicy:悄悄丢掉新任务,不报错。
DiscardOldestPolicy:丢掉队列最老的任务,再尝试提交。
为什么阿里规范禁止 ExecutorsnewFixedThreadPool 和 newSingleThreadExecutor 用无界队列 LinkedBlockingQueue,任务堆积会 OOM;newCachedThreadPool 最大线程数是 Integer.MAX_VALUE,可能创建海量线程。必须手动用 ThreadPoolExecutor 构造。
⑤ 应用场景与例题
例1 核心线程2,队列3,最大线程4,来了6个任务
执行流程怎么走?
① 任务1、2 由核心线程执行;② 任务3、4、5 入队列;③ 任务6 队列满,创建非核心线程执行;④ 此时线程数=4=max。再来第7个任务就触发拒绝策略。
例2 CallerRunsPolicy 的作用
为什么背压场景喜欢用它?
队列和线程都满了之后,任务由提交它的线程(比如 Tomcat 的工作线程)自己执行。这会让提交变慢,等于告诉上游"我扛不住了,你慢点发",自然形成反压,避免系统被压垮。
例3 CPU 密集 vs IO 密集
8核机器做计算和做数据库查询分别配多少线程?
CPU 密集配 8~9 个,多了反而上下文切换浪费;IO 密集(大量等数据库/网络)可以配 20~50 个,因为线程大部分时间在阻塞,不占 CPU。
做题心法背参数不如背流程:核心→队列→max→拒绝,顺序一步都不能乱。
⑥ 高频错误诊断(4 条)
错误1:以为先扩容再排队实际是核心线程满了先入队,队列满了才扩容到 max。写反会导致线程数瞬间暴涨。
错误2:用无界队列new LinkedBlockingQueue() 默认容量是 Integer.MAX_VALUE,任务堆积直接 OOM。必须指定有界容量。
错误3:拒绝策略用 Discard 不报错线上任务悄悄丢了都不知道,至少用 Abort 打日志,或 CallerRuns 反压。
错误4:CPU 密集型配几百线程线程多了上下文切换开销反而大,CPU 密集型线程数 ≈ CPU 核数即可。
⑦ 考点真题演练(5 题)
考点分布
| 考法 | 出题形式 | 应对 |
| 执行流程 | 画任务提交路径 | 核心→队列→max→拒绝 |
| 参数含义 | 问 keepAliveTime 作用 | 控制非核心空闲回收 |
| 拒绝策略 | 问 CallerRuns 行为 | 提交线程自己跑 |
| Executors 风险 | 为什么禁止 | 无界队列/最大线程无界 |
真题basic1. 线程池中任务提交后的处理顺序,正确的是?
真题mid2. ThreadPoolExecutor 的 keepAliveTime 作用于哪类线程?
真题mid3. CallerRunsPolicy 拒绝策略的行为是?
真题hard4. 阿里规范为什么禁止用 Executors.newFixedThreadPool?
真题hard5. 8核机器做 CPU 密集型任务,线程池大小建议?
⑧ 必背知识点卡
七参数:核心/最大/存活/队列/工厂/handler/时间单位
执行序:核心→队列→max→拒绝 顺序不能乱
队列:必须有界,防 OOM 别用无界 LinkedBlockingQueue
拒绝:Abort 默认抛异常 CallerRuns 反压最好用
CPU 密集:N+1 个线程 N=核数
IO 密集:2N 或更多 线程多数在等待
Executors:禁用,手动 ThreadPoolExecutor 阿里规范
⑨ 动手输出:给团队设计一个订单处理线程池
场景:下单后要异步发短信、加积分、推消息,QPS 峰值 500。
① 定性:这些任务都是 IO 密集(调外部接口),线程数可以多一些。
② 参数:核心 20,最大 50,队列容量 200,keepAlive 60 秒,线程工厂起名为 order-pool-。
③ 拒绝:用 CallerRunsPolicy,实在扛不住让 Tomcat 线程自己跑,形成反压。
④ 监控:埋点队列长度、活跃线程数、拒绝次数,超过阈值告警。
⑤ 降级:队列快满时直接返回"系统繁忙",不要让用户等超时。
口述思路"IO 密集就多配线程,但队列要有界、拒绝要反压、监控要跟上。"
⑩ 分层练习(基础 + 中档 + 拔高)
▍基础 6 题
基础1线程池七大参数里,常驻不回收的是?
核心线程 corePoolSize。
基础2任务先入队列还是先扩容?
先入队列,队列满了才扩容到 max。
基础3默认拒绝策略是哪个?
AbortPolicy,抛 RejectedExecutionException。
基础4CallerRunsPolicy 由谁执行任务?
提交任务的线程自己执行。
基础5为什么 CPU 密集型线程不宜太多?
多了上下文切换开销大,反而降低吞吐。
基础6Executors.newCachedThreadPool 的风险?
最大线程数 Integer.MAX_VALUE,可能创建海量线程。
▍中档 5 题
中档1keepAliveTime 对核心线程生效吗?
默认不生效;调用 allowCoreThreadTimeOut(true) 后才生效。
中档2有界队列选 ArrayBlockingQueue 还是 Linked?
Array 有界且预分配数组,性能稳定;Linked 可设定容量但节点对象有 GC 压力。
中档3线程池线程数设为 CPU 核数 N+1,为什么 +1?
偶尔页缺失或安全点停顿时,多一个线程保证 CPU 不空闲。
中档4DiscardOldestPolicy 丢的是哪个任务?
队列队头最老的任务,然后尝试重新提交。
中档5如何给线程池起名字方便排查?
自定义 ThreadFactory,命名前缀如 order-pool-。
▍拔高 5 题
拔高1核心线程数会被回收吗?
默认不会;allowCoreThreadTimeOut(true) 后超过 keepAliveTime 也会回收。
拔高2队列满了非核心线程干完任务后多久退出?
超过 keepAliveTime 且线程数 > corePoolSize 时退出。
拔高3Tomcat 的线程池和 JDK 有何不同?
Tomcat 先开线程到 max 再用队列(JDK 反过来),为了快速响应突发流量。
拔高4如何动态调整线程池参数?
暴露 corePoolSize/maxPoolSize 的 set 方法,结合配置中心热更新。
拔高5任务执行前如何做前置校验?
重写 beforeExecute/afterExecute 方法。
⑪ 记忆口诀 + 7 天复习计划
三句口诀① 核心队列最大拒,顺序一步不能错。② 有界队列反压跑,Executors 不能要。③ CPU 密集核数加一,IO 密集翻倍起。
| 天 | 任务 | 自检 |
| 第 1 天 | 读②③④,画线程池执行流程图 | 七参数能默写 |
| 第 2 天 | 背执行顺序表 + 基础 6 题 | 流程说不错 |
| 第 3 天 | 做中档 6 题,手推一次6任务场景 | 扩容时机对 |
| 第 4 天 | 做拔高 5 题,写一个手动 ThreadPoolExecutor | 代码能跑 |
| 第 5 天 | 做⑦真题 5 题 | 每题限时 2 分钟 |
| 第 6-7 天 | 合书口述拒绝策略四种 | 不看资料全对 |
← 返回软件技术总览