02 线程池掌故:管理并发的秘籍
在上一章,我们学习了线程的基本使用。但有一个问题:线程数量越多越好吗?
答案是 否定的。线程切换需要保存/恢复执行上下文,消耗大量资源。如果无限制创建线程,会导致线程切换频繁、内存耗尽等问题。
因此,需要线程池来统一管理线程。
一、线程池是什么
线程池是一种基于池化思想的线程管理工具:
- 维护多个常驻线程,重复利用
- 避免线程反复创建和销毁的开销
- 控制线程数量,防止资源耗尽
- 支持弹性扩缩容
二、线程池的核心参数
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数,永远不会被回收 |
| maximumPoolSize | 最大线程数,突发任务时临时扩展 |
| keepAliveTime | 空闲线程存活时间 |
| unit | 时间单位(秒/分钟/小时) |
| workQueue | 任务队列,存放待执行任务 |
| threadFactory | 线程工厂,控制如何创建线程 |
| handler | 拒绝策略,任务过多时的兜底方案 |
1. 线程池如何处理任务?
处理流程:
- 线程数 < corePoolSize → 创建新线程执行任务
- 线程数 >= corePoolSize → 任务放入队列等待
- 队列满了 + 线程数 < maximumPoolSize → 创建新线程执行
- 队列满了 + 线程数 = maximumPoolSize → 触发拒绝策略
- 任务高峰期过后 → 回收多余线程至 corePoolSize
2. workQueue 常用类型
| 队列类型 | 特点 |
|---|---|
| SynchronousQueue | 无容量交换队列,线程数轻松达到 maximumPoolSize |
| LinkedBlockingQueue | 无界队列,容量为 Integer.MAX_VALUE,容易 OOM |
| ArrayBlockingQueue | 有界队列,需要设置长度,maximumPoolSize 生效 |
| DelayedWorkQueue | 延时队列,适用于定时线程池 |
使用建议:
- 需要弹性扩缩容 → 使用
ArrayBlockingQueue - 需要任务排队但不限制长度 → 使用
LinkedBlockingQueue(注意 OOM 风险)
3. 拒绝策略
当线程池满了,后续任务会被拒绝。JDK 提供 4 种策略:
| 策略 | 说明 |
|---|---|
| AbortPolicy | 抛出 RejectedExecutionException 异常(默认) |
| DiscardPolicy | 静默丢弃任务,不抛异常 |
| DiscardOldestPolicy | 丢弃队列中最旧的任务 |
| CallerRunsPolicy | 由提交任务的线程自己执行 |
选择建议:
- 日志/统计任务 →
DiscardPolicy - 需要通知调用者 →
AbortPolicy - 必须执行的任务 →
CallerRunsPolicy(注意会阻塞调用者)
4. 核心线程超时回收
默认情况下,核心线程即使空闲也不会被回收。可以通过设置允许回收:
java
threadPoolExecutor.allowCoreThreadTimeOut(true);设置后,核心线程空闲超时也会被回收。
三、JDK 默认线程池(不推荐使用)
1. 定长线程池
java
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);- corePoolSize = maximumPoolSize = 10
- 使用无界队列
- 缺陷:任务堆积会导致 OOM
2. 单线程池
java
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();- 同一时间只执行一个任务
- 缺陷:与定长线程池相同
3. 缓存线程池
java
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();- corePoolSize = 0,maximumPoolSize =
Integer.MAX_VALUE - 使用 SynchronousQueue
- 空闲线程 60 秒后回收
- 缺陷:极限情况下会无限制创建线程
4. 定时线程池
java
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);常用方法:
java
// 延迟 1 秒后执行一次
scheduledExecutorService.schedule(task, 1, TimeUnit.SECONDS);
// 延迟 1 秒后开始,每隔 2 秒执行一次
scheduledExecutorService.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
// 任务执行完后,延迟 2 秒再执行
scheduledExecutorService.scheduleWithFixedDelay(task, 0, 2, TimeUnit.SECONDS);四、线程池状态
| 状态 | 说明 |
|---|---|
| RUNNING | 正常运行,接受新任务 |
| SHUTDOWN | 不接受新任务,执行完已提交任务 |
| STOP | 不接受新任务,不执行已提交任务 |
| TIDYING | 所有任务终止,清理中 |
| TERMINATED | 已终止 |
状态切换:
java
shutdown() // RUNNING → SHUTDOWN
shutdownNow() // RUNNING → STOP
awaitTermination() // 等待进入 TERMINATED五、自定义线程池示例
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(100), // 队列
new MyThreadFactory(), // 线程工厂
new DiscardPolicy() // 拒绝策略
);六、总结
线程池的核心价值:
- 资源控制:防止无限制创建线程
- 性能提升:复用线程,减少创建销毁开销
- 任务管理:队列缓冲、拒绝策略
阿里巴巴规范建议:不要使用 JDK 默认线程池创建方式,应使用 ThreadPoolExecutor 手动配置,避免 OOM 风险。