ThreadPoolExecutor 是 Java 线程池的核心实现类,其完整构造方法包含 7 个核心参数,这些参数共同决定了线程池的行为特性、性能表现和资源消耗。理解这 7 个参数是掌握 Java 线程池的基础,也是面试中的高频考点。
一、完整构造方法
java
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
二、逐个参数详解
1. corePoolSize:核心线程数
定义 :线程池中长期保留的线程数量,即使这些线程处于空闲状态也不会被销毁(除非显式设置allowCoreThreadTimeOut(true))。
工作机制:
- 当提交新任务时,如果当前运行的线程数 < corePoolSize,即使有其他空闲线程,也会创建新的核心线程来执行任务
- 核心线程默认会一直存活在线程池中,不会因为空闲而被回收
- 可以通过
prestartCoreThread()预启动一个核心线程,prestartAllCoreThreads()预启动所有核心线程
关键注意点:
- 核心线程是线程池的 "常驻员工",负责处理大部分常规任务
- 设置
allowCoreThreadTimeOut(true)后,核心线程也会受keepAliveTime限制,空闲超时后会被销毁 - 常见设置建议 :
- CPU 密集型任务:
CPU核心数 + 1(减少线程上下文切换开销) - IO 密集型任务:
2 * CPU核心数(IO 等待时 CPU 空闲,可多开线程) - 混合型任务:根据任务比例拆分,分别创建不同的线程池
- CPU 密集型任务:
2. maximumPoolSize:最大线程数
定义:线程池允许创建的最大线程数量,包括核心线程和非核心线程。
工作机制:
- 当核心线程数已满,且工作队列也已满时,如果当前运行的线程数 < maximumPoolSize,会创建非核心线程来执行任务
- 非核心线程是线程池的 "临时员工",任务处理完后会在空闲一段时间后被销毁
关键注意点:
maximumPoolSize - corePoolSize= 非核心线程的最大数量- 如果
maximumPoolSize == corePoolSize,线程池就是固定大小的(对应Executors.newFixedThreadPool()) - 如果
maximumPoolSize设置为Integer.MAX_VALUE,线程池就是无界的(对应Executors.newCachedThreadPool()),可能会创建大量线程导致 OOM - 设置过小会导致任务堆积,设置过大会导致系统资源耗尽(CPU、内存)
3. keepAliveTime:非核心线程空闲存活时间
定义:非核心线程在空闲状态下的最长存活时间。
工作机制:
- 当线程池中的线程数 > corePoolSize 时,多余的非核心线程如果空闲时间达到
keepAliveTime,就会被终止 - 默认只对非核心线程生效,核心线程不受此限制
关键注意点:
- 可以通过
setKeepAliveTime(long, TimeUnit)动态修改存活时间 - 如果设置了
allowCoreThreadTimeOut(true),核心线程也会受此时间限制 - 常见设置建议:根据任务执行频率调整,任务执行频繁可设置长一点(如 60 秒),减少线程创建销毁的开销;任务执行不频繁可设置短一点,节省系统资源
4. unit:时间单位
定义 :keepAliveTime参数的时间单位,是java.util.concurrent.TimeUnit枚举类的常量。
常用可选值:
TimeUnit.NANOSECONDS:纳秒TimeUnit.MICROSECONDS:微秒TimeUnit.MILLISECONDS:毫秒TimeUnit.SECONDS:秒TimeUnit.MINUTES:分钟TimeUnit.HOURS:小时TimeUnit.DAYS:天
关键注意点 :必须与keepAliveTime配合使用,确保时间单位正确,避免出现 "1 毫秒" 被误写为 "1 秒" 的低级错误。
5. workQueue:工作队列
定义:用于存储等待执行的任务的阻塞队列,所有提交到线程池但尚未开始执行的任务都会存放在这个队列中。
工作机制:
- 当核心线程数已满时,新提交的任务会被加入到工作队列中等待执行
- 队列的类型和容量直接影响线程池的行为
常见队列类型及特点:
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
ArrayBlockingQueue |
有界阻塞队列,基于数组实现,必须指定容量 | 任务量可控的场景,防止任务无限堆积 |
LinkedBlockingQueue |
无界阻塞队列(默认容量Integer.MAX_VALUE),基于链表实现 |
任务执行速度快于提交速度的场景 |
SynchronousQueue |
同步队列,不存储元素,每个插入操作必须等待一个对应的删除操作 | 任务提交速度波动大的场景(如CachedThreadPool) |
PriorityBlockingQueue |
优先级阻塞队列,按照任务的优先级排序执行 | 需要按优先级处理任务的场景 |
关键注意点:
- 强烈不推荐使用无界队列 (如默认的
LinkedBlockingQueue),因为如果任务提交速度远大于处理速度,会导致队列无限增长,最终引发 OOM Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor()都使用了无界的LinkedBlockingQueue,这也是不推荐使用 Executors 创建线程池的主要原因之一
6. threadFactory:线程工厂
定义:用于创建线程的工厂类,线程池中的所有线程都是通过这个工厂创建的。
默认实现 :Executors.defaultThreadFactory(),创建的线程具有以下特点:
- 相同的优先级(
Thread.NORM_PRIORITY) - 非守护线程
- 线程名称格式为
pool-1-thread-1、pool-1-thread-2等
自定义线程工厂的好处:
- 给线程设置有意义的名称,方便排查问题(如
order-process-thread-1) - 设置线程的优先级
- 设置线程是否为守护线程
- 捕获线程中的未捕获异常,避免异常导致线程死亡而无法感知
示例(Guava ThreadFactoryBuilder):
java
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("order-process-thread-%d")
.setDaemon(false)
.setPriority(Thread.NORM_PRIORITY)
.setUncaughtExceptionHandler((t, e) -> {
log.error("线程{}发生未捕获异常", t.getName(), e);
})
.build();
7. handler:拒绝策略
定义:当线程池无法处理新任务时(工作队列已满,且最大线程数已满),采取的处理策略。
工作机制 :当任务提交被拒绝时,会调用RejectedExecutionHandler接口的rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法。
JDK 内置的 4 种拒绝策略:
| 拒绝策略 | 行为 | 适用场景 |
|---|---|---|
AbortPolicy(默认) |
直接抛出RejectedExecutionException异常 |
任务不可丢失,需要明确感知失败的场景 |
CallerRunsPolicy |
由提交任务的线程(调用线程)来执行该任务 | 任务不能丢失,且可以接受调用线程阻塞的场景 |
DiscardPolicy |
直接丢弃任务,不做任何处理 | 非核心任务,丢失也不会影响系统运行的场景 |
DiscardOldestPolicy |
丢弃队列中最旧的任务,然后尝试重新提交当前任务 | 任务有优先级,新任务比旧任务更重要的场景 |
关键注意点:
- 默认的
AbortPolicy会抛出运行时异常,必须捕获处理,否则会导致调用线程崩溃 CallerRunsPolicy会让调用线程执行任务,可能会阻塞调用线程,影响系统的响应速度- 可以实现
RejectedExecutionHandler接口自定义拒绝策略,比如记录日志、持久化任务到数据库、降级处理等
三、线程池完整工作流程(7 个参数协同)
- 提交任务到线程池
- 如果当前运行线程数 <
corePoolSize:创建核心线程执行任务 - 如果当前运行线程数 ≥
corePoolSize:将任务加入workQueue - 如果
workQueue已满:- 如果当前运行线程数 <
maximumPoolSize:创建非核心线程执行任务 - 如果当前运行线程数 ≥
maximumPoolSize:执行handler拒绝策略
- 如果当前运行线程数 <
- 任务执行完成后,非核心线程空闲时间超过
keepAliveTime会被销毁
四、面试高频考点
-
为什么不推荐使用 Executors 创建线程池?
FixedThreadPool和SingleThreadExecutor使用无界队列,可能导致任务无限堆积引发 OOMCachedThreadPool的maximumPoolSize是Integer.MAX_VALUE,可能创建大量线程导致 OOM- 使用 ThreadPoolExecutor 构造方法可以明确指定所有参数,根据业务场景进行精准调优
-
如何合理设置线程池参数?
- 先分析任务类型(CPU 密集型 / IO 密集型 / 混合型)
- 参考公式:
maximumPoolSize = corePoolSize + (队列容量 * 任务平均执行时间 / 任务平均提交间隔) - 优先使用有界队列,避免 OOM
- 自定义线程工厂,方便问题排查
- 根据业务场景选择合适的拒绝策略,或自定义拒绝策略
五、示例:自定义线程池
java
// 获取CPU核心数
int cpuCoreNum = Runtime.getRuntime().availableProcessors();
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCoreNum, // 核心线程数:CPU核心数
cpuCoreNum * 2, // 最大线程数:2*CPU核心数
60L, // 空闲存活时间:60秒
TimeUnit.SECONDS, // 时间单位:秒
new ArrayBlockingQueue<>(1000), // 有界队列,容量1000
new ThreadFactoryBuilder().setNameFormat("business-thread-%d").build(), // 自定义线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用线程执行
);