一、写在前面
参考阿里开发规约,创建线程池一般用ThreadPoolExecutor
在高并发程序中,频繁创建与销毁线程是一种极其低效且不可控的行为。为了解决这个问题,Java 提供了线程池(ThreadPoolExecutor
)这一强大的并发框架。它不仅提高了任务处理的效率,也帮助我们更好地控制系统资源,是现代高性能服务架构中的基石。
本文将从线程池的概念、设计目的、核心实现机制、任务调度流程、Worker 管理机制等方面入手
1.1 线程池是什么
线程池是一种线程复用机制,它维护了一组可以重复使用的工作线程,任务到来时无需创建新线程,而是复用已有线程执行任务,任务执行完毕后线程不会被销毁,而是回到池中待命。
Java 中的核心实现是 java.util.concurrent.ThreadPoolExecutor
。
1.2 线程池解决了什么问题
问题 | 线程池的解决方案 |
---|---|
线程创建销毁开销大 | 线程复用,避免频繁构造/销毁 |
系统资源不可控 | 线程数量上限可设,避免 OOM |
并发请求过多无法及时处理 | 使用阻塞队列缓存任务,异步处理 |
调度和控制困难 | 提供任务调度、拒绝策略、监控接口 |
缺乏任务管理能力 | 可统一提交、执行、取消任务 |
二、线程池核心设计与实现
2.1 总体设计架构
Java 线程池主要由以下组件构成:
┌──────────────┐ submit(Runnable)
│ 主线程调用 ├────────────────┐
└──────────────┘ ▼┌──────────────┐│任务提交 execute│└─────┬────────┘▼┌────────────┐ (1)创建核心线程执行任务│ WorkerSet │ ←────────────────────────┐└────┬───────┘ │▼ │┌────────────────┐ ││ BlockingQueue │ ←───────(2)入队等待执行 │└──────┬─────────┘ │▼ │(3)队列满,扩容线程池 (4)满了,执行拒绝策略▼ ▼┌────────────────┐ ┌────────────────┐│ maximumPoolSize│ │ rejectHandler()│└────────────────┘ └────────────────┘
2.2 生命周期管理
线程池有五种状态,由高位表示,低位为线程数量(workerCount),统一由 ctl
原子变量管理。
状态 | 描述 |
---|---|
RUNNING | 接收新任务,处理队列 |
SHUTDOWN | 拒收新任务,继续处理队列 |
STOP | 拒收新任务,中断执行线程 |
TIDYING | 所有任务执行完,清理中 |
TERMINATED | 清理完成,彻底关闭 |
生命周期状态在任务提交、线程退出、关闭线程池等场景中都会动态变化。
2.3 任务执行机制
2.3.1 任务调度流程(execute)
executor.execute(task) 时的完整流程:
-
线程池运行状态检查,若不是 RUNNING,直接拒绝任务。
-
线程数 < corePoolSize:创建新线程执行任务。
-
线程数 ≥ corePoolSize:尝试将任务放入队列。
-
队列已满 && 线程数 < maximumPoolSize:创建非核心线程执行任务。
-
队列满 && 已达最大线程数:执行拒绝策略(默认抛异常)。
2.3.2 任务缓冲机制
任务队列用于缓存尚未执行的任务:
队列类型 | 特性 |
---|---|
ArrayBlockingQueue | 有界 FIFO 队列,避免内存溢出 |
LinkedBlockingQueue | 默认无界队列,适合任务速率平稳 |
SynchronousQueue | 不存储任务,适用于高并发 |
PriorityBlockingQueue | 支持优先级任务 |
任务是否入队,决定了线程是否扩容。
2.3.3 任务申请(线程取任务)
任务的获取由 getTask()
方法完成:
-
优先从队列中 poll 一个任务
-
若获取失败,且超出 keepAliveTime,线程会退出
-
如果线程池关闭,线程立即退出
2.3.4 任务拒绝策略
当线程池无法接收任务时,会调用 RejectedExecutionHandler
:
策略 | 描述 |
---|---|
AbortPolicy (默认) | 抛出异常 |
DiscardPolicy | 直接丢弃任务 |
DiscardOldestPolicy | 丢弃队列中最老的任务 |
CallerRunsPolicy | 调用线程自己执行任务(削峰) |
合理的拒绝策略有助于保护系统稳定性。
2.4 Worker 线程管理机制
2.4.1 Worker 线程
Worker 是线程池中用于执行任务的线程封装类,它本质是一个实现了 Runnable 接口的任务执行单元,每个 Worker 包含:
-
一个真正的 Thread 对象
-
一个任务任务引用
-
一个 run() 循环体:从队列中不断拉取任务执行
2.4.2 Worker 线程的创建(addWorker)
线程池根据当前任务和线程数决定是否调用 addWorker()
增加线程:
-
当线程数 < corePoolSize:创建核心线程
-
当队列已满 && 线程数 < maximumPoolSize:创建临时线程
使用 ThreadFactory
可定制线程名称、优先级等。
2.4.3 Worker 线程的回收
非核心线程超过 keepAliveTime
会被自动回收:
-
降低资源占用
-
避免长期占用 CPU/内存
-
通过
allowCoreThreadTimeOut(true)
也可使核心线程超时回收
回收逻辑在 getTask()
中实现。
2.4.4 Worker 线程执行任务流程
Worker 启动后,在 run()
方法中循环调用:
while ((task != null || (task = getTask()) != null)) {beforeExecute()try {task.run();} finally {afterExecute()}
}
执行完任务后尝试从队列中取下一个任务,直到线程超时或线程池关闭。
总结
Java 的线程池设计是非常经典的“并发容器”之一,其优雅地处理了:
-
线程的 复用与销毁
-
任务的 排队与拒绝
-
系统的 稳定性与扩展性
它不是简单的任务线程分发器,更是一个具备“智能调度、弹性扩容、精细管控”能力的多线程平台。
附录:推荐配置建议
场景 | 核心参数建议 |
---|---|
接口服务 | corePoolSize = CPU 核心数,队列有限,拒绝策略为 CallerRuns |
IO 密集 | maximumPoolSize 设置略高,使用 SynchronousQueue |
定时任务 | 使用 ScheduledThreadPoolExecutor |
高并发网关 | 使用自定义线程工厂 + 有界队列 + 指标监控 |