线程池核心在于可控调度与边界控制:必须使用有界队列(如arrayblockingqueue)防oom;线程数需按任务类型分层配置——cpu密集型设为cpu核数+1,io密集型设为核数×2~4;拒绝策略应避免默认abortpolicy,推荐callerrunspolicy或自定义可观测策略。

线程池不是“多开几个线程”那么简单,而是围绕任务生命周期构建的一套可控、可测、可伸缩的并发执行体系。核心在于用有限资源承接不确定负载,关键不在堆线程数,而在调度逻辑与边界控制。
任务队列必须是有界的
无界队列(如 LinkedBlockingQueue 默认容量为 Integer.MAX_VALUE)是生产事故高发区。当任务提交速度远超处理速度时,任务持续堆积,内存被迅速吃尽,最终触发 OOM。真实案例中,10 万订单压入无界队列,几秒内就耗光 JVM 堆内存。
- 推荐使用 ArrayBlockingQueue 或带容量限制的 LinkedBlockingQueue(1000)
- 容量设定需结合单任务平均执行时间、峰值 QPS 和可用内存估算。例如:QPS=200,平均耗时 200ms → 理论积压上限 ≈ 200 × 0.2 = 40,预留 3~5 倍缓冲 → 队列设为 200~300 较稳妥
- 切忌依赖“反正队列很大,先放着再说”这种侥幸心理
线程数量要按任务类型分层配置
“CPU 核数 × 2”不是万能公式,必须区分任务性质:
- CPU 密集型(图像压缩、数值计算):线程数 ≈ CPU 核数 + 1。过多线程只会加剧上下文切换,反而降低吞吐
- IO 密集型(HTTP 调用、数据库查询):线程数可设为 CPU 核数 × 2~4。因线程常处于等待状态,适当冗余可提升 CPU 利用率
- 混合型(含同步 IO 和计算逻辑):建议从 CPU 核数 × 2 起步,配合监控动态调优
拒绝策略不能用默认的 AbortPolicy
默认策略直接抛出 RejectedExecutionException,对上游服务来说就是硬错误。在高可用系统中,应选择更柔性的应对方式:
- CallerRunsPolicy:由提交任务的线程自己执行,天然限流,适合突发流量但不希望丢任务的场景
- DiscardOldestPolicy:丢弃队列头任务,给新任务腾位置,适用于时效性强的任务(如实时行情)
- 自定义策略:记录日志 + 上报指标 + 触发告警,把“拒绝”变成可观测事件
工作线程需具备可识别性与可控性
所有线程都叫 pool-1-thread-1 是运维灾难。无法定位慢任务、无法区分业务域、无法做线程级监控。
- 务必通过 ThreadFactory 设置有意义的线程名,例如:
"order-process-pool-%d"、"notify-sender-%d" - 设置线程为守护线程(
setDaemon(true))避免阻塞 JVM 退出 - 统一设置线程优先级(通常保持
NORM_PRIORITY即可),避免人为引入调度偏差











