1086 字
约 3 分钟
5
S05-B05 · 线程池:7 个参数、拒绝策略与动态调整

适用读者:配过线程池但没算过"到底能接多少任务"的人。
前置:S05-B02。
实测环境:OpenJDK 21.0.12.1(2026-11-05)。
覆盖旧文:A-1127(483) A-1126(385) A-2635(273) + 919(96) 920(77) 923(128) 922(91) 1555(128) 1552(118) 1599(200) 1581(116) 387(145) 2663(89) = 2,329 点。
覆盖扩展:E-S05-007(执行流程与拒绝策略)。
上一篇:S05-B04 · ThreadLocal:结构、场景与泄漏
下一篇:S05-J01 · AQS 与 ReentrantLock。


1. 一句话结论

线程池的容量不是"最大线程数",而是"核心线程 + 队列容量"。 超过就按拒绝策略处理——很多线上"任务丢失"就是没算这一条。


2. 实测:核心 2 + 队列 2 = 只能接 4 个

ThreadPoolExecutor ex = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy());

实测输出:

提交任务 1:接受(queue=0)
提交任务 2:接受(queue=0)
提交任务 3:接受(queue=1)
提交任务 4:接受(queue=2)
提交任务 5:**被拒绝**(AbortPolicy)
阶段 行为
任务 1–2 创建核心线程直接跑
任务 3–4 进队列(容量 2)
任务 5 队列满、线程已达 max → 拒绝

注意:这里的 maximumPoolSize = 2 与核心相同,所以"第 3 个任务不会创建新线程"。如果 max > core,队列满后会先扩到 max,再拒绝。


3. 执行流程(E-S05-007)

submit(task)
  ├─ 核心线程未满 → 新建核心线程执行
  ├─ 核心满 → 入队列
  │    ├─ 入队成功 → 等空闲线程取
  │    └─ 队列满 → 尝试扩到 max
  │         ├─ 未到 max → 新建非核心线程
  │         └─ 已到 max → 拒绝策略

四种拒绝策略:

策略 行为 适用
AbortPolicy(默认) 抛 RejectedExecutionException 必须感知失败
CallerRunsPolicy 调用方线程自己跑 天然背压(会拖慢提交方)
DiscardPolicy 静默丢弃 危险(任务无声消失)
DiscardOldestPolicy 丢队列里最老的 只要最新数据

判据:"任务丢了会怎样?" 不能丢 → AbortPolicy + 上层重试/告警;可丢 → Discard*;要背压 → CallerRunsPolicy。


4. 为什么阿里禁用 Executors(A-1581)

工厂方法 隐患
newFixedThreadPool 队列是无界 LinkedBlockingQueue → 任务堆积导致 OOM
newCachedThreadPool max = Integer.MAX_VALUE → 线程爆炸
newSingleThreadExecutor 无界队列(同上)
newScheduledThreadPool 无界延迟队列

结论:手动 new ThreadPoolExecutor,显式指定队列容量与拒绝策略。


5. 参数怎么定(可算的一条)

线程数(CPU 密集)≈ 核数 + 1
线程数(IO 密集)  ≈ 核数 × (1 + 等待时间/计算时间)
队列容量           ≈ 峰值 QPS × 可接受等待时间
参数 建议
corePoolSize CPU 密集 = 核数+1;IO 密集按上式
maximumPoolSize 通常 = core(避免抖动)或略大
keepAliveTime 非核心线程空闲回收时间
workQueue 必须有界(ArrayBlockingQueue(n))
threadFactory 自定义线程名(便于 jstack 定位)
handler 显式指定拒绝策略
allowCoreThreadTimeOut 低峰期回收核心线程

6. 五个误区

误区 真相
"线程池能接无限任务" 核心 + 队列上限(实测 4 个)
"newFixedThreadPool 安全" 无界队列会 OOM
"拒绝策略默认会重试" 默认直接抛异常
"DiscardPolicy 只是少跑一点" 任务静默消失,可能丢关键业务
"线程越多吞吐越高" 受核数与切换成本限制

7. 自检

  1. 实测里为什么第 5 个任务被拒?如果 max > core 会怎样?
  2. 四种拒绝策略各自的适用场景?
  3. 为什么阿里禁用 Executors?举两个具体隐患。
  4. IO 密集型线程数怎么估算?
  5. 队列为什么必须有界?

8. 下一篇

S05-J01 · AQS 与 ReentrantLock 源码

线程池讲完,进阶层开始:ReentrantLock 背后那个"排队等锁"的结构——AQS 的 state 与 CLH 队列。


本篇属于系列《S05 · Java 并发与 JVM》。旧文来源:A-1127/1126/2635/919/920/923/922/1555/1552/1599/1581/387/2663;扩展点 E-S05-007。拒绝策略实测为 OpenJDK 21。

S05-B05 · 线程池:7 个参数、拒绝策略与动态调整
https://www.clxhxhhr.top/posts/5246/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。