Java 线程池(ThreadPoolExecutor)全解析
本文目标:不仅仅停留在“线程池能复用线程”这种表面知识,而是深入到底层实现原理(线程池状态、任务提交流程、Worker 管理)、参数设计思路、源码精讲,并覆盖常见的面试高频题和深入追问。
目录
什么是线程池
为什么需要线程池(核心优势)
Java 线程池体系结构
ThreadPoolExecutor 构造参数详解
线程池运行原理(任务提交与执行流程)
线程池的几种常见实现(Executors 工厂方法)
线程池状态(runState)与生命周期
源码解析(execute → addWorker → runWorker → getTask)
线程池常见使用场景
常见问题与坑点
高频面试题(含追问)
线程池执行流程图
1. 什么是线程池
线程池是一种 线程复用技术,在任务提交前就预先创建若干线程放入池中,任务执行时直接从池中取可用线程运行,减少了线程创建和销毁的开销。
Java 提供的核心实现类是 ThreadPoolExecutor,位于 java.util.concurrent 包。
2. 为什么需要线程池(核心优势)
降低资源消耗:避免频繁创建/销毁线程。
提高响应速度:任务到达时可以立即运行已有线程。
提高线程的可管理性:线程池可统一调度和管理线程,支持超时、定时、队列管理、拒绝策略等。
限制并发数量:避免系统因无限创建线程而 OOM。
3. Java 线程池体系结构
Java 中的线程池核心类和接口关系:
Executor:顶级接口,任务执行器。
ExecutorService:扩展接口,支持生命周期管理、submit、invokeAll 等方法。
AbstractExecutorService:抽象类,提供部分默认实现。
ThreadPoolExecutor:核心实现类,所有常用线程池的基础。
ScheduledThreadPoolExecutor:支持定时/周期任务的线程池。
4. ThreadPoolExecutor 构造参数详解
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
参数解释
corePoolSize:核心线程数,线程池中始终保持存活的线程数量(除非允许超时)。
maximumPoolSize:最大线程数(= 核心线程 + 临时线程)。
keepAliveTime + unit:超过 corePoolSize 的线程空闲多久后会被回收。
workQueue:存放等待执行任务的阻塞队列(如 LinkedBlockingQueue、ArrayBlockingQueue)。
threadFactory:用于创建新线程,可以定制线程名称、优先级、守护属性。
handler:拒绝策略,任务无法执行时的处理方式(常见有 4 种)。
常见拒绝策略
AbortPolicy:直接抛出 RejectedExecutionException(默认)。
CallerRunsPolicy:任务交给提交任务的线程执行。
DiscardPolicy:直接丢弃任务。
DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试执行新任务。
5. 线程池运行原理(任务提交与执行流程)
任务提交:调用 execute() 或 submit()。
判断核心线程数:若运行线程数 < corePoolSize,则新建核心线程执行任务。
队列是否已满:若核心线程已满,则尝试将任务放入队列。
是否超过最大线程数:若队列满且线程数 < maximumPoolSize,则新建线程执行任务。
拒绝策略:若超出 maximumPoolSize 且队列满,则执行拒绝策略。
6. 常见线程池实现(Executors 工厂方法)
⚠️ 注意:面试时经常被问到 为什么不推荐直接使用 Executors 提供的工厂方法?
newFixedThreadPool(int n):固定线程数,使用无界队列,可能导致 OOM。
newCachedThreadPool():线程数不设上限,可能导致过度扩张。
newSingleThreadExecutor():单线程池,保证顺序执行。
newScheduledThreadPool(int n):定时/周期性任务。
👉 正确做法:直接使用 ThreadPoolExecutor 构造函数,显式指定参数。
7. 线程池状态(runState)
ThreadPoolExecutor 内部用一个 ctl 原子变量(高 3 位表示状态,低 29 位表示线程数):
RUNNING:接收新任务,处理队列任务。
SHUTDOWN:不接收新任务,但处理已在队列的任务。
STOP:不接收任务,不处理队列,中断正在执行的任务。
TIDYING:所有任务终止,线程数为 0,准备执行 terminated() 钩子。
TERMINATED:terminated() 执行完成。
8. 源码解析(简要)
execute 流程
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
// 二次检查,避免线程池关闭后任务仍进队列
}
else if (!addWorker(command, false))
reject(command);
}
👉 总结:先创建核心线程 → 尝试入队 → 创建非核心线程 → 拒绝策略。
Worker 内部逻辑
Worker 是一个 AQS 子类,封装了线程和任务。
runWorker 循环调用 getTask() 获取任务,并执行 task.run()。
9. 常见使用场景
Web 服务器:Tomcat / Netty 底层广泛使用线程池。
异步任务执行:如日志处理、消息队列消费者。
高并发场景:复用线程避免创建/销毁的开销。
定时/周期性任务:用 ScheduledThreadPoolExecutor 实现。
10. 常见问题与坑点
为什么不建议用 Executors 工厂方法?
因为内部队列可能是无界的,容易导致 OOM。
线程池里线程数量怎么设置?
CPU 密集型(cpu计算):线程数 ≈ CPU 核数 + 1。
IO 密集型(涉及数据库读写、可能调用外部服务):线程数 ≈ CPU 核数 * 2(或更多,看 IO 阻塞时间)。
线程池满了会怎样?
超过最大线程数 + 队列已满 → 触发拒绝策略。
11. 高频面试题(含追问)
Q1:线程池的核心参数有哪些?
👉 答:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
追问:为什么要有 corePoolSize 和 maximumPoolSize 两个参数?
👉 答:核心线程保证基本并发能力,非核心线程是弹性扩容,用来应对任务高峰。
Q2:线程池任务提交流程?
👉 答:
先创建核心线程;
核心线程满 → 入队列;
队列满 → 创建非核心线程;
超过 maximumPoolSize → 拒绝策略。
追问:如果使用无界队列会怎样?
👉 答:队列不会满,所以 maximumPoolSize 永远不会生效,可能导致堆积大量任务,引发 OOM。
Q3:线程池状态有哪些?
👉 答:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
追问:ctl 是怎么同时存储线程数和状态的?
👉 答:ctl 是一个 AtomicInteger,高 3 位存 runState,低 29 位存 workerCount。通过位运算拆分。
Q4:线程池如何实现线程复用?
👉 答:线程不会销毁,而是执行完任务后从 workQueue 获取下一个任务(runWorker → getTask 循环),直到线程池关闭或线程被回收。
Q5:线程池拒绝策略有哪些?
👉 答:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
追问:如果我需要自定义策略怎么办?
👉 答:实现 RejectedExecutionHandler 接口即可,例如写日志或落盘。
生产环境最佳实践
线程池监控:通过getActiveCount()、getQueue().size()等方法监控线程池状态,及时发现问题
优雅关闭:使用shutdown()平滑关闭,配合awaitTermination()等待任务完成
异常处理:重写afterExecute()方法妥善处理任务执行异常,避免异常被吞没
线程命名:通过自定义ThreadFactory为线程设置有意义的名字,便于问题排查
12. 线程池任务提交流程图
flowchart TD
A["提交任务 execute"] --> B{"线程数 < corePoolSize ?"}
B -- 是 --> C["创建核心线程执行任务"]
B -- 否 --> D{"任务能放入队列吗?"}
D -- 是 --> E["任务进入队列等待"]
D -- 否 --> F{"线程数 < maximumPoolSize ?"}
F -- 是 --> G["创建非核心线程执行任务"]
F -- 否 --> H["执行拒绝策略"]