Skip to content

02 线程池掌故:管理并发的秘籍

在上一章,我们学习了线程的基本使用。但有一个问题:线程数量越多越好吗?

答案是 否定的。线程切换需要保存/恢复执行上下文,消耗大量资源。如果无限制创建线程,会导致线程切换频繁、内存耗尽等问题。

因此,需要线程池来统一管理线程

一、线程池是什么

线程池是一种基于池化思想的线程管理工具:

  • 维护多个常驻线程,重复利用
  • 避免线程反复创建和销毁的开销
  • 控制线程数量,防止资源耗尽
  • 支持弹性扩缩容

二、线程池的核心参数

参数说明
corePoolSize核心线程数,永远不会被回收
maximumPoolSize最大线程数,突发任务时临时扩展
keepAliveTime空闲线程存活时间
unit时间单位(秒/分钟/小时)
workQueue任务队列,存放待执行任务
threadFactory线程工厂,控制如何创建线程
handler拒绝策略,任务过多时的兜底方案

1. 线程池如何处理任务?

处理流程

  1. 线程数 < corePoolSize → 创建新线程执行任务
  2. 线程数 >= corePoolSize → 任务放入队列等待
  3. 队列满了 + 线程数 < maximumPoolSize → 创建新线程执行
  4. 队列满了 + 线程数 = maximumPoolSize → 触发拒绝策略
  5. 任务高峰期过后 → 回收多余线程至 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 风险。