掘地攀登
100.61M · 2026-03-29
在 Java 中,java.util.concurrent.ThreadPoolExecutor 是线程池的核心实现类。掌握其参数配置与运行原理,是应对大厂面试的必备技能。下面从参数详解、工作流程、源码关键逻辑、以及实践配置建议几个方面展开。
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize(核心线程数)allowCoreThreadTimeOut)。maximumPoolSize(最大线程数)keepAliveTime(空闲存活时间)与 unit(时间单位)allowCoreThreadTimeOut(true),则核心线程也会受此参数影响。workQueue(任务队列)用于存放等待执行的任务的阻塞队列。
常见队列:
SynchronousQueue:不存储任务,直接交给线程执行。如果没有空闲线程则创建新线程(常用于无界线程池)。LinkedBlockingQueue:无界队列(默认容量 Integer.MAX_VALUE),可能导致内存溢出,且永远不会触发拒绝策略。ArrayBlockingQueue:有界队列,可防止资源耗尽,需配合合理的 maximumPoolSize 使用。PriorityBlockingQueue:支持优先级排序。threadFactory(线程工厂)handler(拒绝策略)当线程池已关闭,或队列满且线程数已达 maximumPoolSize 时,新提交的任务会触发拒绝策略。
内置策略:
AbortPolicy:直接抛出 RejectedExecutionException(默认)。CallerRunsPolicy:由提交任务的线程(调用者)自己执行任务。DiscardPolicy:静默丢弃任务,不抛出异常。DiscardOldestPolicy:丢弃队列中最老的任务,然后重试提交当前任务。当调用 execute() 或 submit() 提交一个任务时,线程池的处理逻辑如下(以 ThreadPoolExecutor 源码为依据):
text
1. 如果当前运行的线程数 < corePoolSize:
创建新线程执行任务(即使有其他空闲线程)。
2. 否则,尝试将任务加入工作队列:
如果加入成功,等待核心线程或新增线程处理。
注意:加入成功后需要双重检查线程池状态,如果线程池已停止则回滚。
3. 如果加入队列失败(队列已满):
如果当前线程数 < maximumPoolSize:
创建新线程执行任务。
否则:
触发拒绝策略。
关键源码片段(简化版):
java
public void execute(Runnable command) {
int c = ctl.get();
// 1. 当前线程数 < corePoolSize
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 尝试入队
if (isRunning(c) && workQueue.offer(command)) {
// 再次检查状态,如果非运行状态则移除任务并拒绝
if (!isRunning(ctl.get()) && remove(command))
reject(command);
// 如果当前线程数为0,则补充一个非核心线程
else if (workerCountOf(ctl.get()) == 0)
addWorker(null, false);
}
// 3. 队列满,尝试创建非核心线程
else if (!addWorker(command, false))
reject(command);
}
线程池内部通过一个 AtomicInteger(ctl)维护两个信息:
RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED)状态转换:
shutdown())。shutdownNow())。workerCount 为 0,将执行 terminated() 钩子方法。terminated() 执行完毕。线程池中的线程被封装为 Worker 对象,它继承自 AQS 并实现了 Runnable。每个 Worker 会循环从 workQueue 中获取任务并执行:
java
final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
while (task != null || (task = getTask()) != null) {
// 执行任务前加锁(用于中断检测)
task.run();
task = null;
}
// 线程退出(空闲超时或被回收)
}
getTask() 方法中会根据当前线程数、超时设置等,决定是否从队列中阻塞/超时获取任务。如果超时且线程数超过 corePoolSize,则返回 null 导致线程退出。
CPU 核数 + 1。核心线程数 = 最大线程数,队列可选有界队列(如 ArrayBlockingQueue)。2 * CPU 核数。由于 IO 阻塞时间长,线程经常等待,可设置较多线程。LinkedBlockingQueue) :可能导致任务堆积过多,内存溢出。适用场景:任务量可控,或对响应时间要求不高。ArrayBlockingQueue) :配合最大线程数使用,可以触发拒绝策略,防止系统过载。corePoolSize、maximumPoolSize、队列容量,无需重启服务,以适应流量变化。AbortPolicy:默认,直接抛异常,适合必须及时感知失败的业务。CallerRunsPolicy:降级,由调用者线程执行,保证任务不丢,但可能阻塞调用方。DiscardPolicy/DiscardOldestPolicy:适合允许丢失部分不重要的日志、监控数据等。corePoolSize 为 0 会怎样?corePoolSize = 0 时,提交第一个任务会直接创建线程吗?源码中如果 corePoolSize = 0,则第一个任务不会立即创建线程,而是先尝试入队。若队列为空,则创建线程执行。最终线程池中会存在一个线程直到空闲超时。线程数 = Ncpu * (1 + 平均等待时间 / 平均计算时间)。在实践中可通过压测逐步调整。40-模块六-架构演进与团队协作 第40讲-团队 AI 协作模式 - Code Review 工作流 AI 编码规范的推广策略
34-模块五-AI系统架构设计 第34讲-RAG 架构深度优化 - 分块策略 混合检索 重排序在代码场景的实践
2026-03-29
2026-03-29