八股-线程池

线程池

使用线程池的好处

  • 资源管理:控制并发线程数量,防止资源耗尽
  • 线程复用:避免频繁创建/销毁线程的开销,提高响应速度

ThreadPoolExecutor 七大参数

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 临时线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务阻塞队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
参数 说明 注意事项
corePoolSize 常驻核心线程数,即使空闲也保留 设置过小会导致任务堆积,过大浪费资源
maximumPoolSize 线程池最大容量,包含临时线程 必须 ≥ corePoolSize
keepAliveTime 非核心线程空闲后的存活时间 设置合理避免频繁创建销毁
workQueue 存放待执行任务的队列 常用 LinkedBlockingQueueArrayBlockingQueueSynchronousQueue
threadFactory 创建线程的工厂 建议自定义命名,方便排查问题
handler 拒绝策略 默认 AbortPolicy,生产环境建议自定义

任务提交执行流程

  • 提交任务时,优先使用核心线程执行
  • 核心线程都在工作时,任务进入阻塞队列
  • 队列已满,创建临时线程执行任务(当前线程数 < maximumPoolSize)
  • 达到最大线程数,触发拒绝策略

拒绝策略选择

策略 行为 适用场景
AbortPolicy 抛出 RejectedExecutionException 默认策略,需确保有异常处理机制
CallerRunsPolicy 回退给调用者线程执行 不允许任务丢失,能接受主线程阻塞
DiscardPolicy 静默丢弃任务 非关键任务(如日志、心跳)
DiscardOldestPolicy 丢弃最旧任务,尝试提交新任务 实时性要求高,新数据更有价值

线程数配置建议

公式参考

1
2
3
线程数 = CPU核数 * (1 + 线程等待时间 / 线程运行时间)
CPU密集型中线程等待时间无线趋近于0,则线程数 = CPU核数,上面说的CPU核数+1(备用线程)
IO密集型中线程等待时间无限趋近于线程总运行时间,所以线程数 = CPU核数 * (1+1)
  • CPU密集型(计算为主):CPU核数 + 1,对于CPU密集型的任务 (如计算密集型任务),线程数公式:CPU核数+1,因为这些任务主要消耗CPU资源,CPU并不空闲无法切换线程上下文,所以线程数不需要这么多。
  • IO密集型(网络/磁盘IO):2 * CPU核数 或更大,对于任务涉及大量网络IO或磁盘IO,则可以配置更多的线程,线程数公式:2*CPU核数,因为线程在IO阻塞等待时,CPU可以切换线程上下文去处理其他任务。

实际配置原则

  • 公式仅作初始参考,必须压测调优
  • 关注 CPU 利用率(目标 70%-80%)和任务延迟
  • 避免线程过多导致频繁上下文切换

为什么不推荐 Executors

Executors 工厂方法创建的线程池存在 OOM 风险:

方法 问题
newFixedThreadPool() 使用无界队列(LinkedBlockingQueue),任务无限堆积导致 OOM
newSingleThreadExecutor() 同上,单线程 + 无界队列
newCachedThreadPool() 允许创建 Integer.MAX_VALUE 个线程,高并发时创建大量线程导致 OOM
newScheduledThreadPool() 同样使用无界延迟队列

正确做法:直接使用 ThreadPoolExecutor 手动指定参数和边界。


线上监控指标

核心运行指标

  • 活跃线程数:接近 maximumPoolSize 说明负载高,需扩容
  • 队列大小:持续满载说明消费能力不足
  • 拒绝任务数:>0 说明线程池容量不足

性能指标

  • 任务平均耗时:突增可能涉及慢 SQL 或死锁
  • 任务等待时间:过长需增加核心线程数

资源指标

  • CPU 使用率:高吞吐但低 CPU 可能存在 IO 阻塞
  • 线程创建/销毁频率:频繁震荡需调整 keepAliveTime

动态线程池(扩展)

通过分布式配置中心(如 Nacos、Apollo)暴露核心参数,实现运行时动态调整:

  • 核心线程数、最大线程数、队列大小热更新
  • 结合监控指标自动扩缩容
  • 避免重启服务调优

常见内置线程池类型(Executors 工厂方法)

newFixedThreadPool(int nThreads)

  • 特点:固定线程数,使用无界链表队列(LinkedBlockingQueue)
  • 适用:负载稳定的长期任务
  • 风险:任务堆积可能导致 OOM

newCachedThreadPool()

  • 特点:线程数 0~Integer.MAX_VALUE,存活 60 秒,使用同步队列(SynchronousQueue)
  • 适用:短异步小任务,突发流量
  • 风险:高并发下创建大量线程导致 OOM

newSingleThreadExecutor()

  • 特点:单线程 + 无界队列,保证任务顺序执行
  • 适用:需要串行化执行的场景(如日志顺序写入)
  • 风险:队列无界可能 OOM,单点故障

newScheduledThreadPool(int corePoolSize)

  • 特点:支持定时/周期性任务,使用延迟队列(DelayedWorkQueue)
  • 适用:定时任务、心跳检测、缓存刷新
  • 风险:同 Fixed,队列无界

newWorkStealingPool()(JDK 8+)

  • 特点:基于 ForkJoinPool,使用工作窃取算法,线程数默认 CPU 核数
  • 适用:大任务拆分并行计算(分治场景)
  • 注意:任务不应阻塞,否则影响窃取效率

提醒:以上内置线程池均存在边界风险(OOM 或线程数失控),生产环境建议使用 ThreadPoolExecutor 手动配置参数。