沉默是金,总会发光
大家好,我是沉默
“你能说出线程池的七大参数吗?”
你刚背完 corePoolSize、keepAliveTime、handler …… 侃侃而谈。
面试官微微一笑:“那你说说线程池是怎么实现核心线程保活的?keepAliveTime 是怎么起作用的?非核心线程怎么销毁时不误杀还在跑任务的线程?”
你一愣,才意识到:现在面试,参数只是起点,源码才是终点!
今天我们直接带你掌握线程池核心设计思想与实现逻辑,从此拒绝死记硬背,轻松应对面试高阶提问。
-01-
线程池到底是个啥?
线程池就像一个急诊室,病人(任务)不断到来,如何保证高效运转?
举个栗子:
线程池参数 | 急诊室类比 |
corePoolSize | 常驻值班医生 |
maximumPoolSize | 最大能调动的医生总数 |
workQueue | 候诊大厅的座位(等候区) |
keepAliveTime | 临时医生等待多久没人来就走 |
allowCoreThreadTimeOut | 常驻医生也能超时走人 |
threadFactory | 招聘医生的方法 |
handler | 候诊区满时的处理策略 |
线程池的本质是:任务调度 + 资源控制 + 并发保障的动态协同系统
-02-
线程池源码机制
核心组件:Worker
1. Worker 的诞生:addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
}
}
Worker w = new Worker(firstTask);
Thread t = w.thread;
workers.add(w);
t.start();
return true;
}
解析:
- CAS 控制线程安全地增加 worker 数
- core 参数决定用核心还是非核心线程
- 所有 worker 加入 workers 集合统一管理
2. Worker 的使命:runWorker()任务循环执行
final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 允许中断
try {
while (task != null || (task = getTask()) != null) {
w.lock();
try {
beforeExecute(wt, task);
task.run();
afterExecute(task, null);
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
} finally {
processWorkerExit(w, completedAbruptly);
}
}
解析:
- 任务循环 + 获取任务 + 执行任务 + 统计 + 退出处理
- 锁保证任务执行不被中断
- getTask() 是关键:核心/非核心线程分流的源头!
灵魂方法:getTask()
getTask()决定线程命运!
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null) return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
理解:
- 核心线程默认用 take():阻塞等待任务,不会销毁
- 非核心线程用 poll():超时没人叫我,就主动退出
- allowCoreThreadTimeOut 开启后,核心线程也可能超时退出
线程池的 CTL 状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
设计:
- 利用一个整型变量拆成两个字段(高 3 位 runState + 低 29 位 workerCount)
- 状态转移完全受控:RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED
- 原子操作 + 位运算,效率高、并发安全
动态调参:
public void setCorePoolSize(int corePoolSize) {
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else {
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty()) break;
}
}
}
触发:
- 中断空闲线程 → 释放资源
- 创建新线程 → 响应积压任务
- 全程动态联动工作队列与线程数
-03-
面试高频核心问答
Q1:核心线程为什么默认不销毁?
因为 getTask() 中使用了 workQueue.take() 阻塞等待,不超时。
Q2:非核心线程怎么销毁?会误杀任务吗?
使用 poll(keepAliveTime) 获取任务,返回 null 表示超时空闲,才触发销毁,不会杀正在执行的线程。
Q3:shutdown 和 shutdownNow 区别?
shutdown() 会处理完队列再关闭,shutdownNow() 会立即中断所有线程,返回未执行任务列表。
-04-
总结
线程池背后的哲学
原则 | 解释 |
空间换时间 | 任务先进入队列排队,提升吞吐 |
惰性线程创建 | 不到必要时不加线程,避免资源浪费 |
优雅降级 | 拒绝策略保障系统不崩,宁可拒绝任务也不拖垮核心服务 |
状态驱动设计 | 所有行为都基于状态机判断,保障一致性和灵活性 |
掌握线程池源码机制,比会背参数更重要
当你理解了:
- Worker 用 AQS 实现锁和中断控制
- 状态机如何驱动线程生命周期
- getTask() 如何控制线程存亡
那么所谓“线程池七大参数”就不是死记硬背,而是系统设计哲学的具体表现。
下次面试你不妨主动反问:
“您是想听参数的作用,还是更深入理解它背后的实现逻辑?”
这才是真正能让面试官眼前一亮的工程师思维!
-05-
粉丝福利
点点关注,送你互联网大厂面试题库,如果你正在找工作,又或者刚准备换工作。可以仔细阅读一下,或许对你有所帮助!