线程池到底怎么管理线程的?从 ThreadPoolExecutor 到拒绝策略全链路拆解
面试常考:核心参数、任务提交链路、Worker 原理、拒绝策略、Executors 陷阱
一、从一个线上事故说起
某服务用 Executors.newFixedThreadPool(200) 处理请求,运行 2 小时后 OOM。原因:newFixedThreadPool 使用无界队列 LinkedBlockingQueue,堆积了数百万任务导致内存溢出。为什么阿里规约禁止用 Executors 创建线程池?线程池的参数到底怎么影响任务流转?
二、线程池七大核心参数
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
| 参数 | 作用 | 典型值 |
|---|---|---|
| corePoolSize | 常驻线程数 | CPU密集: N+1, IO密集: 2N |
| maximumPoolSize | 线程数上限 | 根据负载设定 |
| keepAliveTime | 非核心线程空闲回收时间 | 60s |
| workQueue | 存放待执行任务 | 有界队列! |
| threadFactory | 创建线程(命名、优先级) | 自定义命名 |
| handler | 任务满+线程满时的处理 | AbortPolicy |
三、任务提交链路全拆解
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();
}
// 2. 线程数 >= core,任务入队列
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))
// 4. 线程数达到 max,执行拒绝策略
reject(command);
}
任务流转图
scss
┌──────────────────┐
│ execute(task) │
└────────┬─────────┘
│
┌────────▼──────────┐
│ 线程数 < │
│ corePoolSize? │──Yes──▶ addWorker(core)
└────────┬──────────┘ 创建核心线程执行
│ No └──▶ return
┌────────▼──────────┐
│ 队列未满? │──Yes──▶ workQueue.offer(task)
│ │ 入队等待
└────────┬──────────┘ └──▶ return
│ No
┌────────▼──────────┐
│ 线程数 < │
│ maximumPoolSize? │──Yes──▶ addWorker(non-core)
└────────┬──────────┘ 创建非核心线程执行
│ No └──▶ return
┌────────▼──────────┐
│ reject(task) │ 执行拒绝策略
└───────────────────┘
关键点:先核心线程 → 再队列 → 再非核心线程 → 最后拒绝。队列优先于非核心线程!
四、ctl 变量:一个 int 存两个值
java
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// ctl 的高 3 位: 线程池状态
// ctl 的低 29 位: 线程数
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 2^29 - 1 ≈ 5亿
// 状态 (高3位)
private static final int RUNNING = -1 << COUNT_BITS; // 111
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000
private static final int STOP = 1 << COUNT_BITS; // 001
private static final int TIDYING = 2 << COUNT_BITS; // 010
private static final int TERMINATED = 3 << COUNT_BITS; // 011
private static int runStateOf(int c) { return c & ~CAPACITY; } // 取高3位
private static int workerCountOf(int c) { return c & CAPACITY; } // 取低29位
private static int ctlOf(int rs, int wc) { return rs | wc; } // 组合
makefile
ctl 字段布局 (32位):
┌─────┬───────────────────────────────────────┐
│ 3位 │ 29位 │
│状态 │ 线程数 │
└─────┴───────────────────────────────────────┘
RUNNING: 111 0000000...0 (-1)
SHUTDOWN: 000 0000000...0 (0)
STOP: 001 0000000...0
TIDYING: 010 0000000...0
TERMINATED: 011 0000000...0
线程池状态流转
scss
┌──────────────────────────────────────────────────────────┐
│ │
│ RUNNING ──shutdown()──▶ SHUTDOWN ──全部完成──▶ TIDYING │
│ │ │ │ │
│ │ │ terminated() │
│ │ │ │ │
│ └──shutdownNow()──▶ STOP ──全部完成──▶ TIDYING──▶ TERMINATED
│ │
│ RUNNING: 接收新任务,处理队列任务 │
│ SHUTDOWN: 不接收新任务,处理队列任务 │
│ STOP: 不接收新任务,不处理队列,中断执行中任务 │
│ TIDYING: 所有任务终止,workerCount=0,即将terminated │
│ TERMINATED: terminated() 执行完毕 │
│ │
└──────────────────────────────────────────────────────────┘
五、Worker:线程与任务的封装
java
private final class Worker extends AbstractQueuedSynchronizer
implements Runnable {
final Thread thread; // Worker 对应的线程
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);
}
}
runWorker:线程的工作循环
java
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 允许中断(state 从 -1 恢复到 0)
boolean completedAbruptly = true;
try {
// 循环获取任务执行
while (task != null || (task = getTask()) != null) {
w.lock(); // Worker 加锁(区分是否在执行任务)
// 线程池状态检查
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;
} finally {
afterExecute(task, thrown); // 钩子方法
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); // 线程退出处理
}
}
getTask:从队列获取任务
java
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 线程池关闭且队列为空,返回 null(线程退出)
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// 是否需要超时控制:
// 1. allowCoreThreadTimeOut=true → 所有线程都超时
// 2. 当前线程数 > corePoolSize → 非核心线程超时
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 线程数超过 max 且允许超时 → 返回 null 退出
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r;
if (timed)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); // 超时获取
else
r = workQueue.take(); // 阻塞获取(核心线程常驻)
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
线程存活与回收逻辑
ini
┌─────────────────────────────────────────────────────────┐
│ Worker 线程循环: │
│ │
│ while (true) { │
│ task = getTask(); │
│ if (task == null) break; // 超时或关闭 → 退出 │
│ task.run(); │
│ } │
│ │
│ getTask 行为: │
│ ├── 核心线程 (wc <= core): workQueue.take() │
│ │ → 队列空则永久阻塞,线程常驻 │
│ │ │
│ ├── 非核心线程 (wc > core): workQueue.poll(keepAlive) │
│ │ → 超时未获取 → 返回 null → 线程退出 │
│ │ │
│ └── allowCoreThreadTimeOut=true: │
│ → 核心线程也走 poll 超时逻辑 │
│ │
│ 线程退出 → processWorkerExit() → 从 workers 移除 │
│ → 如果线程不够 (wc-1 < core),补充新线程 │
└─────────────────────────────────────────────────────────┘
六、拒绝策略
java
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
四种内置策略
java
// 1. AbortPolicy:抛异常(默认)
public static class AbortPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException(
"Task " + r.toString() + " rejected from " + e.toString());
}
}
// 2. CallerRunsPolicy:由提交线程执行
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown())
r.run(); // 谁提交谁执行,形成背压
}
}
// 3. DiscardPolicy:直接丢弃
public static class DiscardPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 什么都不做,任务被静默丢弃
}
}
// 4. DiscardOldestPolicy:丢弃队列最老的任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 丢弃队首
e.execute(r); // 重新提交
}
}
}
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy | 抛 RejectedExecutionException | 默认,需要感知拒绝 |
| CallerRunsPolicy | 提交线程自己执行 | 需要背压、不能丢失 |
| DiscardPolicy | 静默丢弃 | 允许丢失、不关心 |
| DiscardOldestPolicy | 丢弃最老任务 | 新任务更重要 |
自定义拒绝策略
java
public class LogAndStorePolicy implements RejectedExecutionHandler {
private final BlockingQueue<Runnable> backupQueue;
public LogAndStorePolicy(BlockingQueue<Runnable> backupQueue) {
this.backupQueue = backupQueue;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 1. 记录日志
System.out.println("任务被拒绝: " + r + ", 池状态: " + executor);
// 2. 存入备份队列,异步重试
if (!backupQueue.offer(r)) {
throw new RejectedExecutionException("备份队列也满了");
}
}
}
七、Executors 的四大陷阱
1. newFixedThreadPool
java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
// LinkedBlockingQueue 无界!Integer.MAX_VALUE
}
陷阱:队列无界,任务堆积导致 OOM。
2. newSingleThreadExecutor
java
public static ExecutorService newSingleThreadExecutor() {
return new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
// 同样无界队列
}
陷阱:同上。
3. newCachedThreadPool
java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
// maximumPoolSize = Integer.MAX_VALUE!
}
陷阱:最大线程数无上限,大量请求时创建过多线程导致 OOM。
4. newScheduledThreadPool
java
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
// ScheduledThreadPoolExecutor 内部用 DelayQueue (无界)
陷阱:队列无界,任务堆积 OOM。
正确创建方式
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, // corePoolSize
20, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(1000), // 有界队列!
new ThreadFactory() { // 自定义线程工厂
private final AtomicInteger counter = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "biz-pool-" + counter.getAndIncrement());
t.setDaemon(false);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 降级策略
);
八、线程数怎么设置?
公式法
makefile
最佳线程数 = 线程数 × (1 + 等待时间/计算时间)
CPU密集型: N + 1 (等待时间≈0)
IO密集型: 2N 或更多 (等待时间 > 计算时间)
混合型: 拆分为CPU密集和IO密集两个线程池
实际调优
java
// 动态调整参数(运行时可修改)
pool.setCorePoolSize(newCore);
pool.setMaximumPoolSize(newMax);
// 监控关键指标
System.out.println("活跃线程: " + pool.getActiveCount());
System.out.println("队列大小: " + pool.getQueue().size());
System.out.println("已完成: " + pool.getCompletedTaskCount());
System.out.println("最大并发: " + pool.getLargestPoolSize());
九、对比:execute vs submit
| 维度 | execute | submit |
|---|---|---|
| 参数 | Runnable | Runnable/Callable |
| 返回值 | void | Future |
| 异常 | 直接抛出 | 封装在 Future 中 |
| 适用 | 无返回值任务 | 需要返回值/异常处理 |
java
// execute: 异常直接抛出
pool.execute(() -> { throw new RuntimeException("boom"); });
// 异常在 worker 线程抛出,默认打印到 stderr
// submit: 异常封装在 Future 中
Future<?> future = pool.submit(() -> { throw new RuntimeException("boom"); });
try {
future.get(); // 调用 get 时抛出 ExecutionException
} catch (ExecutionException e) {
System.out.println("捕获: " + e.getCause());
}
十、面试高频问题速答
Q1: 为什么阿里规约禁止用 Executors?
newFixedThreadPool 和 newSingleThreadExecutor 用无界队列(Integer.MAX_VALUE),任务堆积 OOM;newCachedThreadPool 最大线程数 Integer.MAX_VALUE,瞬间创建大量线程 OOM。应当用 ThreadPoolExecutor 显式设定有界队列和合理的 maximumPoolSize。
Q2: 核心线程能被回收吗?
默认不能。但当 allowCoreThreadTimeOut=true 时,核心线程在 keepAliveTime 内无任务也会退出。设置方式:pool.allowCoreThreadTimeOut(true)。
Q3: 任务先入队列还是先创建非核心线程?
先入队列。execute 的逻辑是:核心线程满→入队列→队列满→非核心线程→拒绝。这导致如果用 LinkedBlockingQueue(无界),队列永远不满,非核心线程永远不会创建,maximumPoolSize 形同虚设。
Q4: 线程池中线程异常了会怎样?
execute 提交的任务:异常直接抛出,该线程销毁并创建新线程替代。submit 提交的任务:异常封装在 Future 中,get() 时抛出 ExecutionException,线程不销毁继续执行后续任务。
Q5: shutdown 和 shutdownNow 的区别?
shutdown:设为 SHUTDOWN 状态,不接受新任务,但处理完队列中的任务。shutdownNow:设为 STOP 状态,不接受新任务,丢弃队列任务,中断执行中的任务,返回未执行的任务列表。
Q6: workQueue 用什么队列好?
取决于场景:有界队列(ArrayBlockingQueue)推荐------防止 OOM;SynchronousQueue 适合直接传递(CachedThreadPool 用法);PriorityBlockingQueue 适合优先级任务。绝不推荐无界队列。
十一、总结
scss
线程池核心链路:
┌──────────────────────────────────────────────────────┐
│ │
│ execute(task): │
│ 1. wc < core? → addWorker(core) 创建核心线程 │
│ 2. 队列未满? → workQueue.offer(task) 入队 │
│ 3. wc < max? → addWorker(non-core) 非核心线程 │
│ 4. reject(task) → 拒绝策略 │
│ │
│ Worker.run(): │
│ while (task = getTask()) { │
│ task.run(); ← 执行任务 │
│ } │
│ │
│ getTask(): │
│ 核心线程 → take() 永久阻塞 │
│ 非核心 → poll(keepAlive) 超时退出 │
│ │
│ 正确创建: │
│ ThreadPoolExecutor + 有界队列 + 自定义工厂 │
│ 绝不用 Executors (无界队列/无限线程 → OOM) │
│ │
│ 拒绝策略: │
│ Abort(抛异常) / CallerRuns(背压) │
│ Discard(丢弃) / DiscardOldest(丢最老) │
│ │
└──────────────────────────────────────────────────────┘
线程池是 Java 并发的核心组件,理解了 execute 的四步流转、Worker 的工作循环、getTask 的存活逻辑和拒绝策略的触发时机,面试中任何线程池问题都能从源码层面回答。最重要的一条工程经验:永远不要用 Executors,永远用有界队列。