08.4 — Executor 与线程池
定位: 生产环境线程管理的标准方案 — 复用线程、控制并发、避免资源耗尽 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 核心参数、执行流程、拒绝策略、Executors 陷阱、线程池大小设置
一、这是什么?为什么需要它?
为什么需要线程池?
java
// 不用线程池:来一个任务创建一个线程
while (true) {
Socket socket = server.accept();
new Thread(() -> handle(socket)).start();
// 问题:高并发下创建大量线程 → OOM / CPU 耗尽
}线程池解决了 3 个问题:
1. 复用线程 — 避免频繁创建/销毁(线程创建 ≈ 1ms 级开销)
2. 控制并发数 — 防止无限线程导致资源耗尽
3. 管理生命周期 — 统一管理线程的创建、执行、销毁二、原理拆解
2.1 ThreadPoolExecutor 核心参数
java
public ThreadPoolExecutor(
int corePoolSize, // ① 核心线程数
int maximumPoolSize, // ② 最大线程数
long keepAliveTime, // ③ 空闲线程存活时间
TimeUnit unit, // ④ 时间单位
BlockingQueue<Runnable> workQueue, // ⑤ 工作队列
ThreadFactory threadFactory, // ⑥ 线程工厂(可选)
RejectedExecutionHandler handler // ⑦ 拒绝策略(可选)
);参数详解:
① corePoolSize — 核心线程数
即使空闲也保留的线程数(除非 allowCoreThreadTimeOut=true)
② maximumPoolSize — 最大线程数
线程池允许的最大线程数
③ keepAliveTime — 空闲存活时间
超过核心线程数的空闲线程的最长存活时间
④ workQueue — 工作队列
等待执行的任务队列
常见队列:ArrayBlockingQueue / LinkedBlockingQueue / SynchronousQueue
⑤ handler — 拒绝策略
当线程池和队列都满时,如何处理新提交的任务2.2 执行流程
┌─────────────────────────────────────────────────────────────┐
│ ThreadPoolExecutor 执行流程 │
│ │
│ submit(task) │
│ │ │
│ ▼ │
│ ① 当前线程数 < corePoolSize? │
│ ┌──┴──┐ │
│ YES NO │
│ │ │ │
│ ▼ ▼ │
│ 新建核心线程 ② 队列未满? │
│ 执行任务 ┌──┴──┐ │
│ YES NO │
│ │ │ │
│ ▼ ▼ │
│ 入队等待 ③ 当前线程数 < maxPoolSize? │
│ ┌──┴──┐ │
│ YES NO │
│ │ │ │
│ ▼ ▼ │
│ 新建线程 ④ 执行拒绝策略 │
│ 执行任务 │
│ │
│ ⚡ 关键:是先入队,再创建非核心线程! │
│ 很多人误以为"核心 → 最大 → 队列",实际上是: │
│ 核心 → 队列 → 最大 → 拒绝 │
└─────────────────────────────────────────────────────────────┘java
// 代码验证执行流程
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // core
5, // max
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3) // 队列容量 3
);
// 提交 8 个任务
for (int i = 1; i <= 8; i++) {
executor.submit(() -> {
Thread.sleep(Long.MAX_VALUE); // 让线程一直运行
});
}
// 第 1-2 个:新建核心线程执行
// 第 3-5 个:入队等待(队列容量 3)
// 第 6-7 个:新建非核心线程执行(pool: 2→4→5)
// 第 8 个:执行拒绝策略2.3 四种拒绝策略
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AbortPolicy (默认) | 抛 RejectedExecutionException | 必须处理的场景 |
| CallerRunsPolicy | 由提交任务的线程执行 | 慢速降级,让调用方慢下来 |
| DiscardPolicy | 静默丢弃 | 不重要的任务 |
| DiscardOldestPolicy | 丢弃队列中最旧的任务,重试提交 | 新任务比旧任务重要 |
java
// CallerRunsPolicy 示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60, SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadPoolExecutor.CallerRunsPolicy()
// 当线程池满时,提交任务的线程自己执行任务
// 效果:任务处理速度 = 提交速度,自然降级
);2.4 Executors 工厂方法的陷阱
java
// ❌ 陷阱 1:无界队列
ExecutorService pool = Executors.newFixedThreadPool(10);
// 内部使用 LinkedBlockingQueue (无界)
// 如果任务提交速度 >> 处理速度 → 队列无限增长 → OOM
// ❌ 陷阱 2:最大线程无限
ExecutorService pool = Executors.newCachedThreadPool();
// core=0, max=Integer.MAX_VALUE
// 高并发下创建大量线程 → OOM (线程栈内存)
// ❌ 陷阱 3:单线程队列无界
ExecutorService pool = Executors.newSingleThreadExecutor();
// 同样使用无界 LinkedBlockingQueue
// ✅ 最佳实践:手动创建 ThreadPoolExecutor
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10,
20,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列!
new ThreadPoolExecutor.CallerRunsPolicy()
);2.5 线程池大小设置
通用公式(经验值):
CPU 密集型(计算为主):
Nthreads = CPU核心数 + 1(多一个防止缺页中断)
IO 密集型(等待为主):
Nthreads = CPU核心数 * (1 + 平均等待时间 / 平均计算时间)
如果 IO 等待时间是计算时间的 10 倍:
Nthreads = 4核 * (1 + 10) = 44
实际建议:
- 压测调整,没有绝对公式
- 从保守值开始,逐步增加
- 监控队列积压、线程活跃度三、图解全景
┌─────────────────────────────────────────────────────────────┐
│ ThreadPoolExecutor 内部结构 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 任务提交流程 │ │
│ │ │ │
│ │ ┌─────────┐ ① ② ③ │ │
│ │ │ 任务 │ ───→ corePool ───→ workQueue ───→ maxPool│ │
│ │ └─────────┘ (运行中) (等待中) (运行中) │ │
│ │ │ │ │
│ │ │ ④ 队列满 + 达到最大 │ │
│ │ └──→ handler (拒绝策略) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 线程池内部状态 │ │
│ │ │ │
│ │ RUNNING → SHUTDOWN(不再接收) → STOP(立即停止) │ │
│ │ → TIDYING(线程为0) → TERMINATED │ │
│ │ │ │
│ │ shutdown() vs shutdownNow() │ │
│ │ 优雅关闭 vs 立即关闭(返回未执行任务列表) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘四、实战验证
4.1 不同拒绝策略效果
java
// 配置:core=1, max=2, queue=1
// 提交 4 个任务
// AbortPolicy:第 4 个任务抛异常
// CallerRunsPolicy:第 4 个任务由 main 线程执行
// DiscardPolicy:第 4 个任务静默丢弃
// DiscardOldestPolicy:丢弃队列中等待的任务,第 4 个任务入队4.2 监控线程池
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(...);
// 定期监控
ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
monitor.scheduleAtFixedRate(() -> {
System.out.println("PoolSize: " + pool.getPoolSize());
System.out.println("ActiveCount: " + pool.getActiveCount());
System.out.println("QueueSize: " + pool.getQueue().size());
System.out.println("CompletedTaskCount: " + pool.getCompletedTaskCount());
}, 0, 5, TimeUnit.SECONDS);五、面试视角
| 追问 | 答案要点 |
|---|---|
| 线程池的执行顺序? | core → queue → max → 拒绝(很多人答错成 core → max → queue) |
| 为什么禁止 Executors 创建线程池? | 无界队列/无界线程 → OOM 风险 |
| 线程池大小怎么设置? | CPU 密集型 N+1;IO 密集型 2N;实际靠压测 |
| shutdown() 和 shutdownNow() 区别? | shutdown 优雅停止不接受新任务;shutdownNow 中断正在运行 + 返回未执行列表 |
| 核心线程数能动态调整吗? | setCorePoolSize() 和 setMaximumPoolSize() 可在运行时调整 |
📚 相关链接
- **线程基础与创建** — 线程基础
- **线程安全与锁** — 线程安全
- **并发工具类(AQS)** — 并发工具
- ← 返回 **并发索引**