核心线程不被回收,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); // 如果失败,执行拒绝策略
    }
}

  

 

关键点:

  1. 核心线程的创建:如果当前线程数小于 corePoolSize,线程池会尝试创建核心线程(addWorker(command, true))。
  2. 任务队列:如果核心线程已满,任务会被放入任务队列。
  3. 非核心线程:如果任务队列已满,线程池会尝试创建非核心线程(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;
    }
}

  

 

关键点:

  1. 创建 Worker:Worker 是线程池中真正执行任务的线程。
  2. 启动线程:调用 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); // 处理线程退出
        }
    }
}

  

关键点:

  1. 循环获取任务:通过getTask() 方法从任务队列中获取任务。
  2. 执行任务:调用task.run() 执行任务。
  3. 处理线程退出:当没有任务可执行时,调用 processWorkerExit 方法处理线程退出。

4. 第四步:getTask 方法的作用


getTask 是 ThreadPoolExecutor 中的一个核心方法,它的作用是从任务队列中获取任务。getTask方法的核心逻辑可以分为以下几个关键步骤,我们逐个拆解下。

4.1 getTask 的死循环

getTask 方法的开头是一个死循环:

    private Runnable getTask() {
        boolean timedOut = false; // 是否超时    
        for (; ; ) { // 死循环        
            // 省略部分代码...    
        }
    }

 

这个死循环的作用是:

  1. 不断尝试获取任务:线程会一直尝试从任务队列中获取任务,直到满足退出条件。
  2. 处理线程池状态变化:在每次循环中,都会检查线程池的状态(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;
        }
    }

  

关键点:

  1. timed变量:决定了线程是否需要超时退出。如果allowCoreThreadTimeOut为 true,或者当前线程数超过核心线程数(wc > corePoolSize),timed 为 true,线程可能会超时退出。
  2. 线程回收的第二个条件:如果线程数超过最大线程数,或者线程已经超时,并且线程数大于 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. 总结

通过以上分析,我们可以总结出:

  1. 核心线程的“保命”机制:
    • 默认情况下,allowCoreThreadTimeOut 为 false,核心线程的 timed 为 false。
    • 核心线程会调用 workQueue.take(),一直阻塞等待任务,而不会被回收。
  2. 核心线程的回收条件:
    • 如果显式设置 allowCoreThreadTimeOut 为 true,核心线程会调用 workQueue.poll,在超时后退出。
    • 另外,如果线程池关闭,核心线程也会退出。
  3. 死循环的作用:让线程持续获取任务,并动态响应线程池状态变化。

6. 回到文章开头

通过一步步深入源码,我们不仅搞清楚了核心线程为什么默认不会被回收,还理解了死循环的作用和线程退出的条件。这种设计既保证了线程池的性能,又提高了系统的灵活性。

所以,下次面试官问你“核心线程如何保证不被回收”时,你就可以自信地回答:“getTask 通过一个死循环不断尝试获取任务。对于核心线程,timed 为 false,线程会调用 workQueue.take() 一直阻塞等待任务,而不会被回收。如果显式设置 allowCoreThreadTimeOut 为 true,核心线程也会在超时后退出。

 

posted @ 2025-05-25 22:11  IT6889  阅读(45)  评论(0)    收藏  举报