线程池核心参数
候选人小余在面试快手 P6 时,面试官问道:
"线程池有哪些核心参数?它们是怎么配合工作的?"
小余说:"有核心线程数、最大线程数、队列..."面试官追问:"队列有哪几种?它们的适用场景是什么?"
小余答不上来。面试官继续:"keepAliveTime 是针对什么的?allowCoreThreadTimeOut 是什么意思?"
小余彻底卡住了...
一、核心问题:线程池核心参数 🔴
1.1 问题拆解
第一层:七大参数(有哪些?)
"ThreadPoolExecutor 的七大参数是什么?"
考察点:corePoolSize、maximumPoolSize、workQueue、threadFactory、handler、keepAliveTime、allowCoreThreadTimeOut
第二层:协作机制(怎么配合?)
"线程池的执行流程是什么?参数之间怎么配合?"
考察点:execute() 的核心判断逻辑
第三层:队列选择(用什么?)
"BlockingQueue 有哪几种?它们的特点是什么?"
考察点:无界队列、有界队列、同步队列
1.2 ❌ 错误示范
候选人原话 A:"corePoolSize 是最小线程数,线程池不会少于这个数。"
问题诊断:如果没有开启 allowCoreThreadTimeOut,核心线程确实不会被回收。但如果队列满了且 maximumPoolSize > corePoolSize,新线程会被创建,直到达到 maximumPoolSize。
候选人原话 B:"LinkedBlockingQueue 是无界队列,可以存无限多的任务。"
问题诊断:LinkedBlockingQueue 的默认容量是 Integer.MAX_VALUE,实际上是"准无界"。有界队列(如 ArrayBlockingQueue)可以防止 OOM,但需要配置拒绝策略。
1.3 标准回答
P5 级别:七大参数
ThreadPoolExecutor 的七大参数:
public ThreadPoolExecutor(
int corePoolSize, // 1. 核心线程数
int maximumPoolSize, // 2. 最大线程数
long keepAliveTime, // 3. 空闲线程存活时间
TimeUnit unit, // 4. keepAliveTime 的时间单位
BlockingQueue<Runnable> workQueue, // 5. 任务队列
ThreadFactory threadFactory, // 6. 线程工厂
RejectedExecutionHandler handler // 7. 拒绝策略
)
参数详解:
P6 级别:参数协作机制
线程池执行流程(execute):
public void execute(Runnable command) {
int c = ctl.get();
// 1. 如果当前线程数 < corePoolSize,创建核心线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) // true = core thread
return;
c = ctl.get();
}
// 2. 如果核心线程已满,尝试加入队列
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); // 确保至少有一个 worker
}
// 3. 如果队列满,尝试创建非核心线程
else if (!addWorker(command, false)) // false = non-core thread
reject(command); // 拒绝任务
}
执行流程图:
graph TD
A[提交任务] --> B{线程数 < corePoolSize?}
B -->|是| C[创建核心线程<br/>执行任务]
B -->|否| D{队列不满?}
D -->|是| E[加入任务队列]
D -->|否| F{线程数 < maxPoolSize?}
F -->|是| G[创建非核心线程<br/>执行任务]
F -->|否| H[执行拒绝策略]
keepAliveTime 的作用:
// 空闲线程的回收
if (wc > corePoolSize || timed) {
long nl = timeout;
if (wc == corePoolSize) nl = keepAliveTime;
if (worker.tryLock()) { // 尝试获取锁
if (isObsolete(wc, nl)) { // 超过 keepAliveTime 无任务
worker.completedAbruptly = true;
workers.remove(worker);
}
}
}
keepAliveTime 控制的是超过 corePoolSize 的那部分空闲线程的最大存活时间。如果 allowCoreThreadTimeOut = true,核心线程也会在空闲超过 keepAliveTime 后被回收。
P7 级别:队列选择与拒绝策略
任务队列的三种类型:
队列选择的经验法则:
- 任务量大、耗时长:用有界队列 + 饱和策略
- 任务量可控:用
ArrayBlockingQueue 并设置合理容量
- 任务必须被立即处理:用
SynchronousQueue(如线程创建代价高)
拒绝策略:
【面试官心理】
这道题我能问到 P7 级别,是因为线程池参数的选择涉及了性能、资源管理、错误处理的综合权衡。能正确配置队列和拒绝策略的候选人说明他有生产经验的思考。
1.4 追问升级
追问 1:为什么阿里巴巴 Java 规范禁止使用 Executors 创建线程池?
Executors.newFixedThreadPool(n) 和 Executors.newCachedThreadPool() 的问题:
FixedThreadPool:使用 LinkedBlockingQueue(Integer.MAX_VALUE),队列无界,高并发下任务堆积导致 OOM
CachedThreadPool:maximumPoolSize = Integer.MAX_VALUE,高并发下可能创建大量线程导致 OOM
正确做法:使用 new ThreadPoolExecutor(...) 显式配置参数。
追问 2:线程工厂可以做什么?
ThreadFactory factory = new ThreadFactory() {
private AtomicInteger counter = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("MyPool-Worker-" + counter.getAndIncrement());
t.setDaemon(false); // 用户线程,非守护线程
return t;
}
};
线程工厂可以:设置线程名称(方便排查)、设置守护状态、设置优先级、设置未捕获异常处理器。
二、常用线程池配置 🟡
2.1 CPU 密集型
int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
int maxPoolSize = corePoolSize;
ThreadPoolExecutor pool = new ThreadPoolExecutor(
corePoolSize, maxPoolSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("CPU-Pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
2.2 I/O 密集型
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; // 或更多
int maxPoolSize = corePoolSize * 2;
ThreadPoolExecutor pool = new ThreadPoolExecutor(
corePoolSize, maxPoolSize,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder().setNameFormat("IO-Pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
三、生产避坑
3.1 任务堆积导致 OOM
场景:使用 Executors.newFixedThreadPool(10) 处理请求,数据库阻塞后任务堆积,OOM。
根因:LinkedBlockingQueue 默认容量 Integer.MAX_VALUE,任务堆积导致内存耗尽。
解决:使用有界队列 + 合理的拒绝策略。
3.2 线程池拒绝策略的顺序问题
// 错误:CallerRunsPolicy 在核心线程满、队列满、maxPoolSize 满时
// 会由调用方线程执行,这可能导致调用方线程被阻塞
pool.execute(() -> {
pool.execute(() -> innerTask()); // 调用方也是线程池线程 → 死锁风险
});
💡
面试加分点:能说出"JDK 21 引入的虚拟线程(Virtual Threads)使得传统的线程池模式需要重新考虑——虚拟线程的栈是堆内存分配的,创建成本极低,传统的线程池可能被虚拟线程池(Executors.newVirtualThreadPerTaskExecutor())替代",说明他对 JDK 21 有跟进。
⚠️
面试陷阱:被问到"keepAliveTime 设为 0 是什么意思",很多人会说"立即回收"。准确答案是:设为 0 表示非核心线程在执行完任务后立即被回收。如果 allowCoreThreadTimeOut = true,核心线程也会立即被回收。