📋 概述
一句话:线程池是"复用线程、控制并发、防止雪崩"的核心基础设施,
ThreadPoolExecutor是其唯一核心实现。
从一个真实问题切入
"阿里 Java 开发手册明确禁止使用 Executors.newFixedThreadPool(),为什么?"
很多同学的第一反应是"无界队列会 OOM"——没错,但这只是冰山一角。真正的问题是:Executors 工厂方法把关键参数隐藏了,你不知道自己创建的线程池有多大、队列有多长、拒绝时会发生什么。一旦流量突增,轻则任务堆积导致内存溢出,重则线程数爆炸拖垮整个服务。
线程池解决的核心问题:
| 问题 | 没有线程池时 | 有线程池时 |
|---|---|---|
| 资源消耗 | 每个任务 new Thread(),创建/销毁开销大 | 线程复用,创建一次跑 N 个任务 |
| 响应速度 | 等线程创建才能执行 | 任务提交即调度 |
| 并发控制 | 无法限制同时运行的线程数 | 核心线程数 + 最大线程数双重控制 |
| 防雪崩 | 任务无限堆积,内存/CPU 耗尽 | 有界队列 + 拒绝策略兜底 |
💡 生活类比:出租车车队
理解线程池最直观的方式是把它想象成一个出租车公司:
| 出租车公司概念 | 线程池概念 | 类比说明 |
|---|---|---|
| 正式司机 | corePoolSize(核心线程数) | 即使没乘客也保留,随时待命 |
| 临时司机 | maximumPoolSize(最大线程数) | 乘客太多时临时招聘 |
| 候客区 | workQueue(工作队列) | 正式司机都在忙,乘客先排队 |
| 超时解雇 | keepAliveTime(空闲存活时间) | 临时司机等太久没活就走人 |
| 拒载 | RejectedExecutionHandler(拒绝策略) | 车满了、队列满了,新乘客怎么办 |
| 司机培训 | ThreadFactory(线程工厂) | 决定司机怎么培训、穿什么工服 |
🔍 ThreadPoolExecutor 7 大核心参数 ⭐⭐
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
);
参数逐一拆解
| 参数 | 含义 | 类比 | 注意事项 |
|---|---|---|---|
| corePoolSize | 核心线程数——即使空闲也保留 | 正式司机数量 | 设置后不会自动缩小(需 allowCoreThreadTimeOut) |
| maximumPoolSize | 最大线程数——核心+非核心总上限 | 司机总数(正式+临时) | 必须 ≥ corePoolSize |
| keepAliveTime | 非核心线程空闲多久后销毁 | 临时司机等多久没活就走 | 仅对非核心线程生效(除非 allowCoreThreadTimeOut(true)) |
| unit | keepAliveTime 的时间单位 | 小时/分钟/秒 | 通常用 TimeUnit.SECONDS |
| workQueue | 等待执行的任务队列 | 候客区 | ⚠️ 必须有界,否则 OOM |
| threadFactory | 线程创建的工厂 | 人事部门 | 自定义可给线程命名,便于排查 |
| handler | 队列满+线程满时的拒绝策略 | 拒载方式 | 默认 AbortPolicy(抛异常) |
关键设计思想:corePoolSize 和 maximumPoolSize 的分离,让线程池能弹性伸缩——平时用少量核心线程节省资源,高峰时临时扩线程应对突发。
🔍 线程池执行流程 ⭐
当你调用 executor.execute(task) 时,发生了什么?核心原则:先 core → 队列 → max → 拒绝。
重点理解:先入队,再扩容。很多人以为是"线程满了才入队",实际上线程数达到 corePoolSize 后,新任务会先尝试入队,队列满了才创建非核心线程。这个设计是为了避免频繁创建线程——如果每个任务都创建线程,线程池就退化成 new Thread().start() 了。
// ThreadPoolExecutor.execute() 源码简化(JDK 17)
public void execute(Runnable command) {
int c = ctl.get();
// 1. 当前线程数 < corePoolSize → 创建核心线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) // true=核心线程
return;
c = ctl.get();
}
// 2. 核心线程满 → 入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 2.1 入队成功但线程池已关闭 → 移除任务并拒绝
if (!isRunning(recheck) && remove(command))
reject(command);
// 2.2 入队成功且线程数为0 → 创建非核心线程兜底
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // false=非核心线程
}
// 3. 入队失败(队列满)→ 尝试创建非核心线程
else if (!addWorker(command, false))
// 4. 创建失败 → 拒绝
reject(command);
}
addWorker(command, core) 的两步:
- CAS 增加线程计数(
workerCountOf(c) + 1) - 创建
Worker对象并启动线程
Worker 是 ThreadPoolExecutor 的内部类,它实现了 Runnable,同时继承了 AQS(用于中断控制)。每个 Worker 包装一个 Thread,run() 方法里循环从队列取任务执行。
🔍 4 种拒绝策略
当线程数达到 maximumPoolSize 且队列已满时,新提交的任务会被拒绝。JDK 内置 4 种策略:
| 策略 | 行为 | 适用场景 | 代码 |
|---|---|---|---|
| AbortPolicy(默认) | 抛 RejectedExecutionException | 需要感知拒绝,上层处理 | new ThreadPoolExecutor.AbortPolicy() |
| CallerRunsPolicy | 由提交任务的线程执行 | 不丢任务 + 天然削峰 | new ThreadPoolExecutor.CallerRunsPolicy() |
| DiscardPolicy | 静默丢弃,什么都不做 | 允许丢失(如日志采集) | new ThreadPoolExecutor.DiscardPolicy() |
| DiscardOldestPolicy | 丢弃队列头部的任务,重试当前任务 | 总是执行最新任务 | new ThreadPoolExecutor.DiscardOldestPolicy() |
// 示例:CallerRunsPolicy 的效果
// 如果线程池满了,任务会在 main 线程(调用者线程)里执行
// 这会"阻塞"调用者,天然起到削峰作用
ExecutorService pool = new ThreadPoolExecutor(
2, 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
new ThreadPoolExecutor.CallerRunsPolicy() // 调用者执行
);
pool.submit(() -> System.out.println("任务1: " + Thread.currentThread().getName()));
pool.submit(() -> System.out.println("任务2: " + Thread.currentThread().getName()));
pool.submit(() -> System.out.println("任务3: " + Thread.currentThread().getName())); // 队列满,调用者执行
// 预期输出:
// 任务1: pool-1-thread-1
// 任务2: pool-1-thread-2
// 任务3: main ← 调用者线程执行了
阿里规范推荐:自定义拒绝策略——记录日志 + 报警通知 + 持久化到磁盘。
(r, executor) -> {
// 1. 记录日志
log.error("Task rejected, pool: {}, queue: {}/{}",
executor.toString(),
((ArrayBlockingQueue<?>) executor.getQueue()).size(),
((ArrayBlockingQueue<?>) executor.getQueue()).remainingCapacity());
// 2. 报警
alarmService.alert("线程池任务被拒绝");
// 3. 持久化(异步)
asyncPersistenceService.save(((Runnable) r));
}
🔍 BlockingQueue 选型
任务队列是线程池的"缓冲带",选错队列直接影响系统稳定性。
| 队列 | 数据结构 | 容量 | 特点 | 适用场景 |
|---|---|---|---|---|
| ArrayBlockingQueue | 数组 | 有界(必须指定) | FIFO,一把锁控制 | ⭐ 推荐,行为可预测 |
| LinkedBlockingQueue | 链表 | 默认 Integer.MAX_VALUE | 两把锁(put/take 分离) | ⚠️ 必须指定大小 |
| SynchronousQueue | 无存储 | 0(直接交付) | 不缓存,直接给消费者 | CachedThreadPool,高吞吐 |
| PriorityBlockingQueue | 堆 | 无界 | 优先级排序 | 任务有优先级 |
| DelayQueue | 堆 | 无界 | 延迟到期才能取 | ScheduledThreadPool |
// ❌ 危险:无界队列,任务堆积时 OOM
new LinkedBlockingQueue<>() // 默认 Integer.MAX_VALUE
// ✅ 安全:有界队列
new ArrayBlockingQueue<>(1000) // 推荐
new LinkedBlockingQueue<>(1000) // 也可,但需注意两把锁的性能
ArrayBlockingQueue vs LinkedBlockingQueue:
| 对比维度 | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| 底层结构 | 数组 | 链表 |
| 锁机制 | 一把锁(put 和 take 互斥) | 两把锁(put 和 take 独立) |
| 内存分配 | 初始化时一次性分配 | 每次入队分配 Node |
| GC 压力 | 低 | 高(频繁创建 Node) |
| 吞吐量 | 中等 | 高并发时可能更好 |
| 推荐度 | ⭐⭐⭐ | ⭐⭐(必须指定容量) |
🔍 线程池状态 ⭐
ThreadPoolExecutor 内部用一个 AtomicInteger 类型的 ctl 字段同时表示线程池状态和当前线程数:
- 高 3 位:线程池运行状态(RUNNING / SHUTDOWN / STOP / TIDYING / TERMINATED)
- 低 29 位:当前活跃线程数(最多支持约 5.3 亿个线程)
| 状态 | 值 | 接受新任务 | 处理队列中的任务 | 中断空闲线程 | 说明 |
|---|---|---|---|---|---|
| RUNNING | -1 << 29 | ✅ | ✅ | ❌ | 正常运行,可接受新任务 |
| SHUTDOWN | 0 << 29 | ❌ | ✅ | ❌ | 调用 shutdown() 后,不接新任务,继续处理队列 |
| STOP | 1 << 29 | ❌ | ❌ | ✅ | 调用 shutdownNow() 后,不接新任务,中断线程 |
| TIDYING | 2 << 29 | ❌ | ❌ | ❌ | 所有任务终止,workerCount=0,即将执行 terminated() |
| TERMINATED | 3 << 29 | ❌ | ❌ | ❌ | terminated() 钩子执行完毕 |
// ctl 字段的位操作
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 提取状态:高3位
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 提取线程数:低29位
private static int workerCountOf(int c) { return c & CAPACITY; }
为什么用一个 int 同时存状态和线程数? 因为状态和线程数需要原子性地同时更新——如果分成两个变量,可能出现"状态是 STOP,但线程数还没减"的不一致。一个 int 用 CAS 一次搞定。
💻 可跑代码
Demo 1:手动创建线程池(7 参数)
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
// 手动创建线程池,7 个参数全部显式指定
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize: 2个核心线程
4, // maximumPoolSize: 最多4个线程
60, TimeUnit.SECONDS, // 非核心线程空闲60秒后销毁
new ArrayBlockingQueue<>(3), // workQueue: 有界队列,容量3
new ThreadFactory() { // threadFactory: 自定义命名
private final AtomicInteger seq = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "worker-" + seq.getAndIncrement());
}
},
new ThreadPoolExecutor.AbortPolicy() // handler: 拒绝策略(抛异常)
);
// 提交 8 个任务
for (int i = 1; i <= 8; i++) {
final int taskId = i;
try {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()
+ " 执行任务 " + taskId);
try { Thread.sleep(500); } catch (Exception e) {}
});
System.out.println("任务 " + taskId + " 提交成功");
} catch (RejectedExecutionException e) {
System.out.println("任务 " + taskId + " 被拒绝: " + e.getMessage());
}
}
executor.shutdown();
try { executor.awaitTermination(10, TimeUnit.SECONDS); } catch (Exception e) {}
System.out.println("所有任务完成");
}
}
// 预期输出(顺序可能不同):
// 任务 1 提交成功
// 任务 2 提交成功
// 任务 3 提交成功
// 任务 4 提交成功
// 任务 5 被拒绝: Task java.util.concurrent.FutureTask rejected from java.util.concurrent.ThreadPoolExecutor
// 任务 6 被拒绝: ...
// 任务 7 被拒绝: ...
// 任务 8 被拒绝: ...
// worker-1 执行任务 1
// worker-2 执行任务 2
// worker-3 执行任务 3
// worker-4 执行任务 4
// worker-1 执行任务 5(队列中取)
// ...
// 所有任务完成
Demo 2:submit 返回 Future
import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.List;
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10)
);
// submit 返回 Future
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "任务结果";
});
// 阻塞获取结果
String result = future.get(); // 阻塞直到完成
System.out.println("结果: " + result);
// invokeAll:等待所有任务完成
List<Callable<Integer>> tasks = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
final int num = i;
tasks.add(() -> {
Thread.sleep(200);
return num * 10;
});
}
List<Future<Integer>> futures = executor.invokeAll(tasks);
for (Future<Integer> f : futures) {
System.out.print(f.get() + " "); // 10 20 30 40 50
}
System.out.println();
// invokeAny:任一完成即可
String winner = executor.invokeAny(tasks);
System.out.println("最先完成的返回: " + winner);
executor.shutdown();
}
}
// 预期输出:
// 结果: 任务结果
// 10 20 30 40 50
// 最先完成的返回: 30(或其他,取决于执行速度)
Demo 3:4 种拒绝策略对比
import java.util.concurrent.*;
public class ThreadPoolDemo3 {
public static void main(String[] args) {
// AbortPolicy:抛异常
testRejectPolicy("AbortPolicy", new ThreadPoolExecutor.AbortPolicy());
// CallerRunsPolicy:调用者执行
testRejectPolicy("CallerRunsPolicy", new ThreadPoolExecutor.CallerRunsPolicy());
// DiscardPolicy:静默丢弃
testRejectPolicy("DiscardPolicy", new ThreadPoolExecutor.DiscardPolicy());
// DiscardOldestPolicy:丢弃最老的
testRejectPolicy("DiscardOldestPolicy", new ThreadPoolExecutor.DiscardOldestPolicy());
}
static void testRejectPolicy(String name, RejectedExecutionHandler handler) {
System.out.println("=== " + name + " ===");
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
handler
);
for (int i = 1; i <= 5; i++) {
final int taskId = i;
try {
executor.execute(() -> {
try { Thread.sleep(1000); } catch (Exception e) {}
System.out.println(" 执行: " + Thread.currentThread().getName() + " task-" + taskId);
});
} catch (RejectedExecutionException e) {
System.out.println(" 任务 " + taskId + " 被拒绝(抛异常)");
}
}
executor.shutdown();
try { executor.awaitTermination(5, TimeUnit.SECONDS); } catch (Exception e) {}
System.out.println();
}
}
// 预期输出(AbortPolicy 会抛异常,其他不会):
// === AbortPolicy ===
// 执行: pool-1-thread-1 task-1
// 执行: pool-1-thread-1 task-2(从队列取)
// 任务 3 被拒绝(抛异常)
// 任务 4 被拒绝(抛异常)
// 任务 5 被拒绝(抛异常)
//
// === CallerRunsPolicy ===
// 执行: pool-1-thread-1 task-1
// 执行: main task-3(调用者线程执行)
// 执行: pool-1-thread-1 task-2
// 执行: main task-4(调用者线程执行)
// 执行: main task-5(调用者线程执行)
//
// === DiscardPolicy ===
// 执行: pool-1-thread-1 task-1
// 执行: pool-1-thread-1 task-2
// (任务 3/4/5 静默丢弃,无任何输出)
//
// === DiscardOldestPolicy ===
// 执行: pool-1-thread-1 task-1
// 执行: pool-1-thread-1 task-5(丢弃队列头部的 task-2,重试 task-5)
Demo 4:自定义 ThreadFactory(线程命名)
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolDemo4 {
// 自定义线程工厂:命名 + 守护线程 + 优先级
static class NamedThreadFactory implements ThreadFactory {
private final String prefix;
private final boolean daemon;
private final AtomicInteger seq = new AtomicInteger(1);
public NamedThreadFactory(String prefix, boolean daemon) {
this.prefix = prefix;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, prefix + seq.getAndIncrement());
t.setDaemon(daemon);
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new NamedThreadFactory("order-handler-", false)
);
// 提交任务,观察线程名
for (int i = 1; i <= 4; i++) {
final int id = i;
executor.submit(() -> {
System.out.println("任务 " + id + " 在 "
+ Thread.currentThread().getName() + " 上执行");
});
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
}
// 预期输出:
// 任务 1 在 order-handler-1 上执行
// 任务 2 在 order-handler-2 上执行
// 任务 3 在 order-handler-1 上执行
// 任务 4 在 order-handler-2 上执行
Demo 5:线程池监控
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolDemo5 {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadFactory() {
private final AtomicInteger seq = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "mon-" + seq.getAndIncrement());
}
}
);
// 提交任务模拟负载
for (int i = 0; i < 15; i++) {
final int id = i;
try {
executor.execute(() -> {
try { Thread.sleep(500); } catch (Exception e) {}
});
} catch (RejectedExecutionException e) {
System.out.println("任务 " + id + " 被拒绝");
}
}
// 监控指标
Thread.sleep(200);
System.out.println("===== 线程池监控 =====");
System.out.println("当前线程数: " + executor.getPoolSize());
System.out.println("活跃线程数: " + executor.getActiveCount());
System.out.println("队列大小: " + executor.getQueue().size());
System.out.println("队列剩余容量: " + executor.getQueue().remainingCapacity());
System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
System.out.println("总任务数: " + executor.getTaskCount());
System.out.println("历史最大线程数: " + executor.getLargestPoolSize());
System.out.println("是否已关闭: " + executor.isShutdown());
System.out.println("是否已终止: " + executor.isTerminated());
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
}
}
// 预期输出:
// 任务 11 被拒绝
// 任务 12 被拒绝
// 任务 13 被拒绝
// 任务 14 被拒绝
// ===== 线程池监控 =====
// 当前线程数: 4
// 活跃线程数: 4
// 队列大小: 10
// 队列剩余容量: 0
// 已完成任务数: 0
// 总任务数: 15
// 历史最大线程数: 4
// 是否已关闭: false
// 是否已终止: false
⚠️ 常见问题与踩坑
Q1:Executors.newFixedThreadPool() 有什么问题?
答:内部用 LinkedBlockingQueue<>()(默认容量 Integer.MAX_VALUE),等同于无界队列。任务堆积时不会触发拒绝策略,只会不断往内存里塞,最终 OOM。同理,newSingleThreadExecutor() 也是无界队列。
// ❌ 源码:Executors.newFixedThreadPool()
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()); // 无界队列!
// ✅ 手动创建
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1000)); // 有界队列
Q2:Executors.newCachedThreadPool() 有什么问题?
答:corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,用 SynchronousQueue。每个任务都会创建新线程,高并发时线程数暴增,CPU 和内存都会被打爆。
Q3:核心线程数怎么算?
答:根据任务类型区分:
| 任务类型 | 公式 | 示例 |
|---|---|---|
| CPU 密集型 | N + 1(N=CPU 核数) | 8 核 → 9 个线程 |
| IO 密集型 | N × 2 或 N / (1 - W)(W=IO 等待占比) | 8 核,W=0.6 → 20 个线程 |
| 混合型 | 拆分为两个独立线程池 | CPU 池 + IO 池 |
注意:这只是起点,实际要通过压测调整。Runtime.getRuntime().availableProcessors() 获取 CPU 核数。
Q4:为什么"先入队再扩容"而不是"先扩容再入队"?
答:这是 ThreadPoolExecutor 的核心设计决策。如果先创建非核心线程再入队,每个任务都会触发线程创建(退化成 new Thread().start())。先入队让已有线程有机会复用,只有队列满了才创建新线程——线程复用最大化。
Q5:线程池状态流转的触发条件?
答:
shutdown()→ 进入 SHUTDOWN:不接新任务,处理完队列shutdownNow()→ 进入 STOP:不接新任务,中断所有线程- 队列空 + workerCount=0 → 进入 TIDYING:所有任务完成
terminated()钩子执行完 → 进入 TERMINATED
Q6:为什么不用 Executors 创建线程池(阿里规范)?
答:阿里规范的完整理由:
- FixedThreadPool / SingleThreadPool:无界队列,OOM
- CachedThreadPool:线程数无上限,CPU/内存爆炸
- 无法设置拒绝策略:Executors 工厂方法无法传入自定义 handler
- 无法给线程命名:默认线程名
pool-N-thread-M,排查问题困难
Q7:execute() 和 submit() 有什么区别?
答:
| 对比 | execute(Runnable) | submit(Callable/Runnable) |
|---|---|---|
| 返回值 | void | Future<T> |
| 异常处理 | 直接抛出(未捕获异常处理器) | 异常封装在 Future 里,调 get() 时抛出 |
| 取消任务 | 不支持 | future.cancel(true) |
| 获取结果 | 不支持 | future.get() 阻塞获取 |
Q8:shutdown() 和 shutdownNow() 怎么选?
答:
- 优雅关闭(推荐):
shutdown()+awaitTermination(timeout)→ 超时后shutdownNow() - 立即关闭:
shutdownNow()中断所有线程,返回未执行的任务列表 - 关键区别:
shutdown()会处理完队列里的任务;shutdownNow()直接中断
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
List<Runnable> pending = executor.shutdownNow();
System.out.println("强制关闭," + pending.size() + " 个任务未执行");
}
Q9:线程池如何动态调整参数?
答:JDK 17 支持运行时调整,无需重启:
executor.setCorePoolSize(16); // 调大核心线程数
executor.setMaximumPoolSize(32); // 调大最大线程数
executor.setKeepAliveTime(120, TimeUnit.SECONDS);
但注意:不能减少 corePoolSize(如果当前线程数 > 新值,不会自动缩减)。
Q10:线程池任务嵌套会导致死锁吗?
答:会。如果线程池 A 的任务里提交任务到同一个线程池 A,当所有线程都在等内部任务完成时,就会死锁。解决方案:不同业务用不同线程池隔离。
🎯 最佳实践
Do / Don't 对照表
| ✅ Do | ❌ Don't |
|---|---|
手动创建 ThreadPoolExecutor | 用 Executors.newXxxThreadPool() |
指定有界队列 ArrayBlockingQueue | 用无界 LinkedBlockingQueue<>() |
自定义 ThreadFactory 给线程命名 | 用默认工厂,线程名 pool-N-thread-M |
| 设置合理的拒绝策略 | 只用默认的 AbortPolicy |
| 不同业务用不同线程池 | 所有任务共用一个线程池 |
shutdown() + awaitTermination() 关闭 | 直接 shutdownNow() 或不关闭 |
监控 getActiveCount/getQueue().size() | 不关心线程池状态 |
阿里规范速查
- 线程池必须用
ThreadPoolExecutor创建,不允许用Executors - 线程池命名:
业务名-线程池用途-N,如order-pool-8 - 拒绝策略:不能丢弃的任务用
CallerRunsPolicy,可丢弃的用自定义策略(日志+报警) - 线程池隔离:不同业务独立线程池,避免互相影响
- 线程数:CPU 密集型 N+1,IO 密集型 2N,压测验证后调整
💡 面试要点
- 线程池 7 大参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲存活时间)、unit(时间单位)、workQueue(工作队列)、threadFactory(线程工厂)、handler(拒绝策略)
- 执行流程:先 core → 队列 → max → 拒绝。注意是"先入队再扩容"
- 4 种拒绝策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃最老)
- 为什么禁用 Executors:FixedThreadPool 无界队列 OOM,CachedThreadPool 线程数无上限
- 线程数设置:CPU 密集 N+1,IO 密集 2N,混合型拆分为两个线程池
- ctl 字段:AtomicInteger,高 3 位存状态,低 29 位存线程数
- 线程池 5 种状态:RUNNING → SHUTDOWN → TIDYING → TERMINATED(STOP 由 shutdownNow 触发)
- Worker 内部类:继承 AQS,实现 Runnable,封装 Thread + firstTask
- execute vs submit:execute 无返回值异常直接抛,submit 返回 Future 异常封装在 Future 里
- 关闭线程池:
shutdown()+awaitTermination()组合,超时后shutdownNow()
📝 总结
| 核心知识点 | 一句话记忆 |
|---|---|
| 线程池价值 | 复用线程、控并发、防雪崩 |
| 7 大参数 | core / max / keepAlive / unit / queue / factory / handler |
| 执行流程 | 先 core → 队列 → max → 拒绝 |
| 拒绝策略 | Abort 抛异常、CallerRuns 调用者执行、Discard 丢弃、DiscardOldest 丢最老 |
| Executors 风险 | 无界队列 OOM、线程数无上限、无法自定义 |
| 状态机 | RUNNING → SHUTDOWN → TIDYING → TERMINATED |
| 线程数设置 | CPU 密集 N+1,IO 密集 2N |
| 最佳实践 | 手动创建、有界队列、线程命名、业务隔离 |
一句话心法:线程池不是万能药,参数设错比不用还危险——核心数 × 2 + 有界队列 + 合理拒绝是起步配置,压测才是最终答案。
📖 外部参考
- 📖 OpenJDK 17 ThreadPoolExecutor 源码 — 官方实现
- 📖 Java Concurrency in Practice — Brian Goetz — 并发编程圣经
- 📖 阿里 Java 开发手册:线程池规约 — 行业规范
- 📖 Java 线程池实现原理及其在美团业务中的实践 — 美团实践