一、引言:为什么需要线程池?
在 Java 中,线程的创建和销毁并非"免费"操作------每个线程的创建都伴随着操作系统内核级的系统调用,默认栈大小为 1MB。如果每次任务都新建线程,频繁的创建销毁会带来巨大的性能开销。
线程池正是为了解决这一问题而生的池化 技术:预先创建一批线程放入"池"中,任务到来时直接复用已有线程,无需等待创建过程。它的核心优势在于:
-
降低资源消耗:复用已创建的线程,避免频繁创建销毁
-
提高响应速度:任务到达时无需等待线程创建
-
提高线程可管理性:统一分配、调优和监控
-
提供任务排队与拒绝能力:控制并发上限,保护系统稳定性
核心设计思想 :基于生产者-消费者模型 ------提交任务的线程是生产者,线程池中的工作线程是消费者,任务队列(
BlockingQueue)是中间的缓冲区。
二、七大核心参数详解
ThreadPoolExecutor 提供了最完整的构造方法,包含 7 个核心参数:
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
2.1 corePoolSize ------ 核心线程数
线程池的常驻核心线程 数量。可以理解为公司的正式员工 ------无论有没有任务,只要线程池存活,这些线程就会一直存在(除非设置了 allowCoreThreadTimeOut(true))。
-
如果设置为 0,表示没有任务时线程池中无线程
-
线程池初始化时不会立即创建 核心线程,而是在任务提交时懒加载创建
2.2 maximumPoolSize ------ 最大线程数
线程池允许创建的最大线程数量 。可以理解为公司的正式员工 + 临时工的总和。
-
当核心线程已满且任务队列已满时,线程池会创建新线程直到达到该值
-
必须大于等于
corePoolSize
2.3 keepAliveTime + unit ------ 空闲存活时间
非核心线程 空闲后的存活时间。当线程空闲时间超过该值时,多余线程会被销毁,直到线程数等于 corePoolSize。
-
如果设置了
allowCoreThreadTimeOut(true),核心线程也会受此参数影响 -
适用于任务量波动大的场景
2.4 workQueue ------ 任务队列
用于存储等待执行的任务的阻塞队列。常见实现及特点:
|-------------------------|-----------------|---------------|
| 队列类型 | 特点 | 适用场景 |
| ArrayBlockingQueue | 有界,基于数组,FIFO | 任务量可控,需限制队列长度 |
| LinkedBlockingQueue | 有界/无界,基于链表,FIFO | 吞吐量要求高,任务量波动大 |
| SynchronousQueue | 不存储元素,直接交接 | 任务执行时间短,并发量高 |
| PriorityBlockingQueue | 无界,支持优先级 | 需按优先级执行任务 |
| DelayQueue | 无界,延迟出队 | 定时任务、超时处理 |
2.5 threadFactory ------ 线程工厂
用于创建线程的工厂。默认使用 Executors.defaultThreadFactory()。生产环境建议自定义命名,便于问题定位。
2.6 handler ------ 拒绝策略
当线程池无法接受新任务时(线程数已达最大且队列已满,或线程池已关闭),执行的处理策略。JDK 提供了四种内置策略:
|-----------------------|---------------------------------|-----------|
| 策略 | 行为 | 适用场景 |
| AbortPolicy(默认) | 抛出 RejectedExecutionException | 严格要求任务不丢失 |
| CallerRunsPolicy | 调用者线程直接执行任务 | 减缓任务提交速度 |
| DiscardPolicy | 静默丢弃任务 | 允许部分任务丢失 |
| DiscardOldestPolicy | 丢弃队列头部的任务,重试提交 | 优先处理新任务 |
三、(了解)源码深度剖析
3.1 ctl ------ 一箭双雕的状态控制器
ThreadPoolExecutor 最精妙的设计之一,是用一个 AtomicInteger 同时记录线程池运行状态 和工作线程数量。
java
// 核心状态控制字段
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 低 29 位表示工作线程数量,高 3 位表示线程池状态
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 000 111...111 (29个1)
// 五种运行状态(高3位)
private static final int RUNNING = -1 << COUNT_BITS; // 111 000...000
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000 000...000
private static final int STOP = 1 << COUNT_BITS; // 001 000...000
private static final int TIDYING = 2 << COUNT_BITS; // 010 000...000
private static final int TERMINATED = 3 << COUNT_BITS; // 011 000...000
// 解包方法
private static int runStateOf(int c) { return c & ~CAPACITY; } // 获取状态
private static int workerCountOf(int c) { return c & CAPACITY; } // 获取线程数
private static int ctlOf(int rs, int wc) { return rs | wc; } // 打包
为什么要这样设计?
将两个变量合并为一个 AtomicInteger ,对 ctl 的 CAS 操作可以原子性地同时修改状态和线程数 ,避免了加锁。workerCount 的理论最大值是 2^29 - 1 ≈ 5.36 亿,在当前硬件条件下完全够用。
五种状态的含义:
|----------------|-----------------------------------------------|
| 状态 | 含义 |
| RUNNING | 接受新任务,处理队列任务 |
| SHUTDOWN | 拒绝新任务,处理队列任务 |
| STOP | 拒绝新任务,丢弃队列任务,中断正在执行的任务 |
| TIDYING | 所有任务已终止,workerCount = 0,即将执行 terminated() |
| TERMINATED | terminated() 已执行完成 |
状态转换路径:
java
RUNNING → SHUTDOWN (调用 shutdown())
RUNNING/SHUTDOWN → STOP (调用 shutdownNow())
SHUTDOWN → TIDYING → TERMINATED (队列和线程都为空)
STOP → TIDYING → TERMINATED (队列为空)
3.2 execute() ------ 任务提交的核心流程
execute() 是线程池最核心的方法,它的执行逻辑分为三步:
java
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 步骤1:工作线程数 < corePoolSize → 创建核心线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get(); // 创建失败则重新获取 ctl
}
// 步骤2:线程池运行中 && 任务入队成功
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 双重检查:入队后可能线程池已关闭
if (!isRunning(recheck) && remove(command))
reject(command); // 回滚并拒绝
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 无工作线程时创建一个
}
// 步骤3:队列已满 → 尝试创建非核心线程
else if (!addWorker(command, false))
reject(command); // 创建失败 → 执行拒绝策略
}
流程解读:
-
核心线程优先:工作线程数小于核心数时,直接创建新线程执行任务
-
队列缓冲:核心线程已满时,任务入队等待
-
双重检查:入队后重新检查状态,防止线程池在入队期间关闭
-
扩容:队列已满时,创建非核心线程(不超过最大线程数)
-
拒绝:以上都失败时,执行拒绝策略
3.3 addWorker() ------ 线程创建的核心
addWorker 是线程池中最复杂的方法之一,负责原子性地增加工作线程计数并启动线程。
java
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 检查线程池状态:非 RUNNING 状态时,只有在 SHUTDOWN 且 firstTask==null 且队列非空时才允许
if (rs >= SHUTDOWN &&
!(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 检查线程数是否超过上限
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS 增加工作线程数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // CAS 失败,重试
if (runStateOf(c) != rs)
continue retry;
}
}
// 创建工作线程并启动
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w); // 加入工作线程集合
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 启动线程
workerStarted = true;
}
}
} finally {
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
关键设计:
-
双重循环 + CAS:外层循环检查状态,内层循环 CAS 增加计数
-
core参数 :true表示以corePoolSize为上限,false表示以maximumPoolSize为上限 -
加锁添加 :
workers是HashSet,操作时需要加mainLock保证线程安全
3.4 Worker ------ 工作线程的封装
Worker 是 ThreadPoolExecutor 的内部类,它同时实现了 Runnable 和继承了 AQS:
java
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
final Thread thread; // 实际的执行线程
Runnable firstTask; // 初始任务(可为 null)
volatile long completedTasks; // 已完成任务数
Worker(Runnable firstTask) {
setState(-1); // 禁止中断直到 runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this); // 核心执行逻辑
}
// ...
}
为什么 Worker 继承 AQS?
Worker 通过 AQS 实现锁 机制,在执行任务时加锁,确保任务执行的互斥性------一个 Worker 同时只能执行一个任务。
3.5 runWorker() + getTask() ------ 线程的核心循环
runWorker 是工作线程的主循环 ,而 getTask 负责从队列中获取任务:
java
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 允许中断
boolean completedAbruptly = true;
try {
// 核心循环:不断获取任务并执行
while (task != null || (task = getTask()) != null) {
w.lock();
// 检查线程池状态,若为 STOP 则中断线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); // 钩子方法:执行前
Throwable thrown = null;
try {
task.run(); // 执行任务!
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown); // 钩子方法:执行后
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 检查是否需要关闭
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 是否启用超时回收:核心线程超时 或 线程数 > 核心数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// timed ? 超时获取 : 阻塞获取
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
核心机制:
-
线程复用 :
while循环不断从队列获取任务执行,一个线程可以执行多个任务 -
超时回收 :
timed为true时,poll超时返回null,触发线程退出 -
阻塞 vs 超时 :核心线程使用
take()永久阻塞,非核心线程使用poll(keepAliveTime)超时退出 -
钩子方法 :
beforeExecute和afterExecute允许子类扩展
3.6 拒绝策略的源码实现
四种拒绝策略的实现非常简洁:
java
// AbortPolicy:直接抛异常
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " + e.toString());
}
}
// CallerRunsPolicy:调用者线程直接执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run(); // 注意:是 run() 不是 start()!
}
}
}
// DiscardPolicy:静默丢弃
public static class DiscardPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 什么都不做
}
}
// DiscardOldestPolicy:丢弃队列头部任务,重试提交
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
注意 :
CallerRunsPolicy调用的是Runnable.run()而非Thread.start(),不会创建新线程,由提交任务的线程同步执行。
四、钩子方法 ------ 扩展的无限可能
ThreadPoolExecutor 提供了三个受保护的钩子方法,允许子类在不修改核心逻辑的情况下扩展功能:
java
// 任务执行前调用
protected void beforeExecute(Thread t, Runnable r) { }
// 任务执行后调用(无论是否抛出异常)
protected void afterExecute(Runnable r, Throwable t) { }
// 线程池完全终止后调用
protected void terminated() { }
典型应用场景:
-
记录任务执行耗时(监控)
-
清理
ThreadLocal变量 -
收集统计信息
-
资源初始化和释放
java
// 自定义线程池:带监控
public class MonitorThreadPool extends ThreadPoolExecutor {
private final AtomicLong totalTime = new AtomicLong(0);
private final AtomicLong taskCount = new AtomicLong(0);
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
// 在 ThreadLocal 中记录开始时间
MDC.put("startTime", String.valueOf(System.currentTimeMillis()));
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
long startTime = Long.parseLong(MDC.get("startTime"));
long cost = System.currentTimeMillis() - startTime;
totalTime.addAndGet(cost);
taskCount.incrementAndGet();
MDC.remove("startTime");
}
public double getAvgTaskTime() {
long count = taskCount.get();
return count == 0 ? 0 : totalTime.get() / (double) count;
}
}
⚠️ 警告 :钩子方法中不要抛出未捕获的异常,否则会导致工作线程退出。
五、生命周期管理
5.1 shutdown() vs shutdownNow()
|-----------------|-----------------------------|-------------------------|
| 方法 | 行为 | 状态变化 |
| shutdown() | 不再接受新任务,继续处理队列中已有任务 | RUNNING → SHUTDOWN |
| shutdownNow() | 不再接受新任务,丢弃队列中所有任务,中断正在执行的线程 | RUNNING/SHUTDOWN → STOP |
java
// 优雅关闭示例
ExecutorService pool = new ThreadPoolExecutor(...);
// 第一阶段:禁止新任务提交
pool.shutdown();
try {
// 第二阶段:等待已有任务完成(最多等待 60 秒)
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
// 第三阶段:强制关闭
pool.shutdownNow();
// 再次等待
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("线程池未能正常关闭");
}
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
六、Executors 的陷阱与自定义线程池
6.1 为什么禁止使用 Executors?
《阿里巴巴 Java 开发手册》明确规定:线程池不允许使用 Executors 去创建,而要通过 ThreadPoolExecutor 的方式。
|---------------------------|-------------------------------------------------------------------------|------------------|
| Executors 方法 | 底层实现 | 风险 |
| newFixedThreadPool | corePoolSize = maxPoolSize,LinkedBlockingQueue(无界) | 队列无限堆积 → OOM |
| newSingleThreadExecutor | 同上 | 同上 |
| newCachedThreadPool | corePoolSize = 0,maxPoolSize = Integer.MAX_VALUE,SynchronousQueue | 线程无限创建 → OOM |
| newScheduledThreadPool | 同上 | 同上 |
6.2 正确的自定义方式
java
public class ThreadPoolConfig {
public static ExecutorService createCustomPool() {
// 获取 CPU 核心数
int processors = Runtime.getRuntime().availableProcessors();
// CPU 密集型:corePoolSize = CPU核心数 + 1
// I/O 密集型:corePoolSize = CPU核心数 * 2(或更大)
int corePoolSize = processors + 1;
int maxPoolSize = corePoolSize * 2;
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // ✅ 有界队列,防止 OOM
new NamedThreadFactory("biz-pool"), // ✅ 命名线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // ✅ 合理的拒绝策略
);
}
}
// 自定义命名线程工厂
class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
NamedThreadFactory(String name) {
this.namePrefix = name + "-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
七、最佳实践与避坑指南
✅ 推荐做法
-
始终使用
ThreadPoolExecutor自定义创建,明确所有参数 -
使用有界队列 (如
ArrayBlockingQueue),防止任务堆积导致 OOM -
自定义线程工厂并命名,便于问题定位
-
根据任务类型设置线程数:
-
CPU 密集型:
corePoolSize = CPU核心数 + 1 -
I/O 密集型:
corePoolSize = CPU核心数 * 2(需根据实际 I/O 等待时间调整)
-
-
选择合适的拒绝策略 :关键业务用
CallerRunsPolicy,可容忍丢失用DiscardPolicy -
优雅关闭 :使用
shutdown()+awaitTermination()组合 -
监控线程池状态 :通过
getActiveCount()、getQueue().size()等方法监控
八、总结
ThreadPoolExecutor 是 Java 并发编程中最核心的类之一,它的设计堪称经典:
1. 精巧的状态管理 :用一个 AtomicInteger 的 32 位同时记录运行状态 (高 3 位)和线程数量(低 29 位),通过 CAS 实现无锁更新。
2. 分层的任务处理:核心线程优先 → 队列缓冲 → 非核心线程扩容 → 拒绝策略,四层机制平滑应对流量波动。
3. 优雅的线程复用 :runWorker + getTask 循环让工作线程持续从队列获取任务,实现线程的无限复用。
4. 灵活的扩展能力:钩子方法、自定义线程工厂、自定义拒绝策略,赋予了无限扩展的可能。
|----------|--------------------------------|
| 维度 | 设计精髓 |
| 状态控制 | ctl 高 3 位状态 + 低 29 位线程数 |
| 任务执行 | 核心线程 → 队列 → 非核心线程 → 拒绝 |
| 线程复用 | runWorker 循环 + getTask 取任务 |
| 扩展机制 | 钩子方法 + 自定义工厂 + 自定义拒绝策略 |
理解 ThreadPoolExecutor 的源码,不仅能帮你写出更健壮的并发代码,更能让你在面对高并发场景时,知道如何精准地配置线程池参数 ,在资源消耗 与系统吞吐量之间找到最佳平衡点。
一句话总结 :
ThreadPoolExecutor是 Java 线程池的"万能钥匙"------它用精妙的设计在线程管理 、任务调度 和资源控制之间取得了完美的平衡,是所有高并发系统的基石。
提问:核心线程优先 → 队列缓冲 → 非核心线程扩容 → 拒绝策略,为什么不是先创建非核心线程,再队列缓冲呢?面试该如何回答
这是权衡"资源开销"与"响应速度"后的设计选择。 优先用队列缓冲,是为了避免线程的频繁创建与销毁,因为线程是重量级资源;而先创建非核心线程再入队,会导致线程数快速膨胀,严重浪费系统资源。
下面我把这个问题的面试回答,拆解成三个递进的层次。你可以直接参考第三层的话术。
第一层:最直观的理解(资源开销)
"线程池的设计者认为,阻塞队列的入队操作,其代价远低于创建一个新线程。 因为创建线程需要分配栈内存、进行系统调用(
pthread_create),开销很大。所以,线程池的策略是'先尝试用轻量级的缓冲(队列)来应对突发流量,只有在缓冲区也满了,说明压力确实很大时,才使用创建新线程这种'重型武器'来扩容。这能最大化地复用线程资源,控制系统的资源消耗。"
第二层:从任务处理效率看(响应时间)
"如果改成'先创建非核心线程,再队列缓冲',意味着当核心线程繁忙时,系统会立即为每个新任务都创建一个新线程。这会让线程数量非常快地达到
maximumPoolSize。一旦达到上限,所有新任务又会被放入队列。这会导致线程的频繁创建和销毁,反而增加了系统的上下文切换开销,降低了吞吐量。""而当前的顺序(核心线程满 → 入队)则提供了一个缓冲平滑期。在队列未满之前,系统不会创建新线程,这给了系统一个应对短暂流量高峰的机会,避免因为瞬间的高并发而过度扩容,造成资源浪费。"
第三层:与阻塞队列特性的配合(核心设计意图)
"这里面有一个容易被忽略的关键点:
BlockingQueue的offer方法是非阻塞的。当核心线程满时,调用offer尝试将任务放入队列,如果队列未满,任务就成功入队了,整个过程非常轻量。如果先创建新线程,就需要将任务提交给一个新创建的线程去执行,这本身就是一个重量级操作。""线程池的设计者希望最大程度地利用已有的核心线程来处理任务。因为线程的创建和销毁是有成本的,
corePoolSize正是为了维持一个稳定的'常驻工人'数量。只有当任务多到连队列这个'缓冲区'都塞不下时,才说明确实超出了系统的常规处理能力,这时才需要临时工(非核心线程)来帮忙。'队列缓冲'是线程池伸缩性管理的第一道防线,它确保了系统在面对突发流量时,优先选择'排队等待'而非'盲目扩容'。"
一句话总结:因为线程是重型资源,队列缓冲是轻量级操作。优先入队是'节流',尽量复用已有线程;只有在队列压力过大时,才'开源'创建新线程。这个顺序是线程池进行资源管理和流量控制的核心策略。