核心线程不被回收,90%的人都挂!

核心线程不被回收,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,核心线程也会在超时后退出。”

相关推荐

雩邑怎么念?探寻古地名背后的文化密码
体育在线365

雩邑怎么念?探寻古地名背后的文化密码

📅 07-30 👁️ 2351
日式烧鸟
365体育怎么打不开网址

日式烧鸟

📅 07-27 👁️ 2129
网事如风网咖
365bet足球即时比分网

网事如风网咖

📅 07-09 👁️ 2650