核心线程不被回收,90%的人都挂!
本引用仅用学习,禁用商用
引用自 https://mp.weixin.qq.com/s/fJsYrkBIN6QEK_Xwzsz0eg
今天分享一个字节跳动的面试真题:如何保证线程池核心线程不被回收?
很多人觉得简单,回答 “默认不回收” 就结束了。结果面试官追问实现原理,立马就傻眼。
其实,这题藏着线程池核心设计逻辑,能吃透源码,面试直接加分!
1. 第一步:从 execute 方法开始
execute方法是线程池的入口,负责提交任务。我们来看一下它的源码:
public class Main {
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)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
第三步:尝试创建非核心线程执行任务
else if (!addWorker(command, false))
reject(command); // 如果失败,执行拒绝策略
}
}
关键点:
- 核心线程的创建:如果当前线程数小于
corePoolSize,线程池会尝试创建核心线程(addWorker(command, true))。 - 任务队列:如果核心线程已满,任务会被放入任务队列。
- 非核心线程:如果任务队列已满,线程池会尝试创建非核心线程(
addWorker(command, false))
2. 第二步:addWorker 方法的作用
addWorker 是创建线程的核心方法。我们来看一下它的源码:
public class Main {
private boolean addWorker(Runnable firstTask, boolean core) {
// 省略部分代码...
Worker w = null;
try {
w = new Worker(firstTask);// 创建 Worker
final Thread t = w.thread;
if (t != null) {
workers.add(w); // 将 Worker 添加到线程集合
t.start(); // 启动线程
}
} finally {
if (t == null) // 如果创建失败,回滚
addWorkerFailed(w);
}
return t != null;
}
}
关键点:
- 创建 Worker:Worker 是线程池中真正执行任务的线程。
- 启动线程:调用
t.start()启动线程,线程会执行 Worker 的 run 方法,进而调用 runWorker。
3. 第三步:runWorker 方法的作用
runWorker 是线程执行任务的核心逻辑。我们来看一下它的源码:
public class Main {
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask; // 获取初始任务
w.firstTask = null; // 清空初始任务
w.unlock(); // 允许中断
boolean completedAbruptly = true;
try {
// 循环获取任务并执行
while (task != null || (task = getTask()) != null) {
w.lock(); // 加锁
try {
task.run(); // 执行任务
} finally {
task = null; // 清空任务
w.unlock(); // 解锁
}
}
completedAbruptly = false; // 正常退出
} finally {
processWorkerExit(w, completedAbruptly); // 处理线程退出
}
}
}
关键点:
- 循环获取任务:通过getTask() 方法从任务队列中获取任务。
- 执行任务:调用task.run() 执行任务。
- 处理线程退出:当没有任务可执行时,调用
processWorkerExit方法处理线程退出。
4. 第四步:getTask 方法的作用
getTask 是 ThreadPoolExecutor 中的一个核心方法,它的作用是从任务队列中获取任务。getTask方法的核心逻辑可以分为以下几个关键步骤,我们逐个拆解下。
4.1 getTask 的死循环
getTask 方法的开头是一个死循环:
private Runnable getTask() {
boolean timedOut = false; // 是否超时
for (; ; ) { // 死循环
// 省略部分代码...
}
}
这个死循环的作用是:
- 不断尝试获取任务:线程会一直尝试从任务队列中获取任务,直到满足退出条件。
- 处理线程池状态变化:在每次循环中,都会检查线程池的状态(RUNNING、SHUTDOWN、STOP 等),确保线程的行为符合线程池的当前状态。
在死循环中,getTask 方法的核心逻辑可以分为以下几个步骤:
4.2 检查线程池状态
int c = ctl.get();
int rs = runStateOf(c);
// 检查线程池状态
if(rs >= SHUTDOWN && (rs >=STOP || workQueue.isEmpty())) {
decrementWorkerCount(); // 减少线程数
return null; // 返回 null,线程退出
}
- 如果线程池状态为 SHUTDOWN 或 STOP,并且任务队列为空,线程会回收。
- 这是线程回收的第一个条件。
4.3 判断是否允许超时回收
public static void main(String[] args) {
int wc = workerCountOf(c);
// 判断是否允许核心线程超时退出
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) // 减少线程数
return null; // 返回 null,线程退出
continue;
}
}
关键点:
- timed变量:决定了线程是否需要超时退出。如果allowCoreThreadTimeOut为 true,或者当前线程数超过核心线程数(wc > corePoolSize),timed 为 true,线程可能会超时退出。
- 线程回收的第二个条件:如果线程数超过最大线程数,或者线程已经超时,并且线程数大于 1 或任务队列为空,线程会回收。
4.4 从任务队列中获取任务
public static void main(String[] args) {
try {
// 从任务队列中获取任务
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if (r != null)
return r; // 返回任务
timedOut = true; // 标记为超时
} catch (InterruptedException retry) {
timedOut = false; // 重置超时标记
}
}
- 如果timed为true,线程会调用
workQueue.poll(),在keepAliveTime 时间内等待任务。 - 如果timed为false,线程会调用
workQueue.take(),一直阻塞等待任务。 - 如果获取到任务,返回任务;否则,标记为超时。
5. 总结
通过以上分析,我们可以总结出:
- 核心线程的“保命”机制:
- 默认情况下,allowCoreThreadTimeOut 为 false,核心线程的 timed 为 false。
- 核心线程会调用 workQueue.take(),一直阻塞等待任务,而不会被回收。
- 核心线程的回收条件:
- 如果显式设置 allowCoreThreadTimeOut 为 true,核心线程会调用 workQueue.poll,在超时后退出。
- 另外,如果线程池关闭,核心线程也会退出。
- 死循环的作用:让线程持续获取任务,并动态响应线程池状态变化。
6. 回到文章开头
通过一步步深入源码,我们不仅搞清楚了核心线程为什么默认不会被回收,还理解了死循环的作用和线程退出的条件。这种设计既保证了线程池的性能,又提高了系统的灵活性。
所以,下次面试官问你“核心线程如何保证不被回收”时,你就可以自信地回答:“getTask 通过一个死循环不断尝试获取任务。对于核心线程,timed 为 false,线程会调用 workQueue.take() 一直阻塞等待任务,而不会被回收。如果显式设置 allowCoreThreadTimeOut 为 true,核心线程也会在超时后退出。”

浙公网安备 33010602011771号