线程池参数必须共同解释。提交任务时,ThreadPoolExecutor 先补到核心线程数,再进入队列,队列满后才增加到最大线程数,
最后执行拒绝策略。使用无界队列时,maximumPoolSize 通常不会生效。
Table of contents
运行饱和实验
下载线程池饱和样例下载, 运行:
java -cp target/classes dev.elaine.concurrency.ThreadPoolSaturationSample
实验只有一个工作线程和一个队列槽。第一项任务占住线程,第二项排队,第三项被拒绝,输出 拒绝任务数=1。Latch 让时序
确定,不依赖 sleep 猜测线程是否已经启动。
五个核心参数
| 参数 | 决定什么 | 常见风险 |
|---|---|---|
corePoolSize | 稳态保留工作线程 | 过大增加上下文切换 |
maximumPoolSize | 队列满后的线程上限 | 无界队列下几乎无效 |
workQueue | 等待容量与顺序 | 过大隐藏过载并增加延迟 |
keepAliveTime | 非核心空闲线程回收 | 过短导致频繁创建 |
RejectedExecutionHandler | 饱和时怎样失败 | 静默丢任务或阻塞关键线程 |
线程工厂应设置可识别名称和未捕获异常处理器。任务内部抛出的异常若被 Future 捕获,不一定到达未捕获异常处理器;必须
观察任务失败指标。
按任务模型估算线程数
CPU 密集任务的线程数通常接近可用核心数。阻塞任务可使用更多线程,但上限仍受下游容量、内存和延迟预算约束。一个常见 起点是:
线程数 ≈ CPU 核数 × 目标利用率 × (1 + 等待时间 / 计算时间)
它只是测量起点,不是配置公式。等待时间分布、容器 CPU 配额、下游连接池和任务优先级都会改变结果。
拒绝策略是业务协议
AbortPolicy抛RejectedExecutionException,适合让调用方明确处理失败。CallerRunsPolicy让提交线程执行,形成自然减速,但可能阻塞事件循环或持锁线程。DiscardPolicy静默丢弃,只有允许丢失且有独立指标时才使用。DiscardOldestPolicy丢弃队头,可能删除最陈旧也最重要的任务。
不要在自定义拒绝器中无限阻塞;这会把线程池拥塞扩散到上游并制造级联故障。
动态调节需要闭环
setCorePoolSize 等 API 可以在线修改参数,但控制器要有采样窗口、上下限、冷却时间和回滚。监控活跃线程、队列深度、
任务年龄、拒绝率、执行时间和下游饱和度。只根据 CPU 自动加线程会把数据库压垮。
下一步
阅读Future 与 CompletableFuture 异步编排,为线程池任务增加 结果、超时、异常和取消协议。