线程池
使用线程池的好处
- 资源管理:控制并发线程数量,防止资源耗尽
- 线程复用:避免频繁创建/销毁线程的开销,提高响应速度
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 |
存放待执行任务的队列 |
常用 LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue |
| 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 手动配置参数。