线程池中线程回收是如何处理的?

205 阅读3分钟

分析&回答

ThreadPoolExecutor 中线程执行getTask()返回null,就会被回收。
我们来看看getTask方法是怎么处理的:

/**
 * Performs blocking or timed wait for a task, depending on
 * current configuration settings, or returns null if this worker
 * must exit because of any of:
 * 1. There are more than maximumPoolSize workers (due to
 *    a call to setMaximumPoolSize).
 * 2. The pool is stopped.
 * 3. The pool is shutdown and the queue is empty.
 * 4. This worker timed out waiting for a task, and timed-out
 *    workers are subject to termination (that is,
 *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
 *    both before and after the timed wait, and if the queue is
 *    non-empty, this worker is not the last thread in the pool.
 *
 * @return task, or null if the worker must exit, in which case
 *         workerCount is decremented
 */
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?  // timed = true 表示存在非核心线程
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        // 这个if条件成立的前提:必须存在非核心线程
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null; // 这个null就是非核心线程被销毁的原因
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r; // 能取到任务就返回,否则设置timedOut进去死循环
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

结论

通过上面代码分析,可以得出结论:

  1. 线程数大于corePoolSize,当队列无task或者线程池状态异常时return null
  2. 线程数小于等于corePoolSize,getTask不会返回null,异常情况除外

你如果设置了keepAliveTime并且设置executor.allowCoreThreadTimeOut(false),当线程池的数量超过corePoolSize时,超过的部分,多余即空闲的线程的存活时间超过keepAliveTime就会被回收。

回收分两种场景:

1、未调用shutdown() ,RUNNING状态下全部任务执行完成的场景

线程数量大于corePoolSize,线程超时阻塞,超时唤醒后CAS减少工作线程数,如果CAS成功,返回null,线程回收。否则进入下一次循环。当工作者线程数量小于等于corePoolSize,就可以一直阻塞了。

2、调用shutdown() ,全部任务执行完成的场景

shutdown() 会向所有线程发出中断信号,这时有两种可能:

  1. 所有线程都在阻塞:中断唤醒,进入循环,都符合第一个if判断条件,都返回null,所有线程回收。
  2. 任务还没有完全执行完:至少会有一条线程被回收。在processWorkerExit(Worker w, boolean completedAbruptly)方法里会调用tryTerminate(),向任意空闲线程发出中断信号。所有被阻塞的线程,最终都会被一个个唤醒,回收。

反思&扩展

核心线程

所谓的核心线程与非核心线程,在线程池的数据结构中并没有明确区分,只要线程池剩余的线程数小于等于corePoolSize,那么剩下的线程都可以称为核心线程;

设置是否回收在保活时间后依然没没有任务执行核心线程
executor.allowCoreThreadTimeOut(true) 该属性缺省值为false


喵呜面试助手: 一站式解决面试问题,你可以搜索微信小程序 [喵呜面试助手] 或关注 [喵呜刷题] -> 面试助手 免费刷题。如有好的面试知识或技巧期待您的共享!