线程池中的空余线程是如何被回收的

简介: 线程池中的空余线程是如何被回收的


首先要知道在线程池中空余线程被回收的条件:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过 keepAliveTime。还有就是线程池执行 Task 的流程(这里借用《Java 并发编程的艺术》这本书中的一张图):

JUC 中的线程池使用很简单,但是源码还是有一定的复杂度的,那么这个地方从何下手呢。首先需要猜测判断回收条件的时机。根据上面的回收条件可以很自然的想到先去看看 getTask() 方法,这个方法就是从工作队列中获取 Task:

/**
  * Performs blocking or timed wait for a task, depending on
  * current configuration settings, or returns null if this worker
 4 * must exit because of any of:
 5 * 1. There are more than maximumPoolSize workers (due to
 6 *    a call to setMaximumPoolSize).
 7 * 2. The pool is stopped.
 8 * 3. The pool is shutdown and the queue is empty.
 9 * 4. This worker timed out waiting for a task, and timed-out
10 *    workers are subject to termination (that is,
11 *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
12 *    both before and after the timed wait, and if the queue is
13 *    non-empty, this worker is not the last thread in the pool.
14 *
15 * @return task, or null if the worker must exit, in which case
16 *         workerCount is decremented
17 */
18  private Runnable getTask() {
19    //上次从队列中 poll() 是否超时
20    boolean timedOut = false; // Did the last poll() time out?
21
22    for (;;) {
23        int c = ctl.get();
24        int rs = runStateOf(c);
25
26        // Check if queue empty only if necessary.
27        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
28            decrementWorkerCount();
29            return null;
30        }
31                //从名称中可以判断,工作的线程数
32        int wc = workerCountOf(c);
33
34        // Are workers subject to culling?  从这里开始,值得关注
35          //是否剔除 worker?核心线程是否超时或工作线程数大于核心线程数
36              // allowCoreThreadTimeOut:If false (default), core threads stay alive even when idle.If true, core threads use keepAliveTime to time out waitingfor work.
37        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
38                //由于可能在这个过程中执行了 setMaximumPoolSize() 方法。
39        //((如果工作线程数大于最大线程数)或者(上次从队列中 poll()是否超时 且 需要剔除工作线程))且(工作线程数大于 1 或 工作队列是空的)
40        if ((wc > maximumPoolSize || (timed && timedOut))
41            && (wc > 1 || workQueue.isEmpty())) {
42            if (compareAndDecrementWorkerCount(c))
43                return null;
44            continue;
45        }
46
47      //poll() 和 take() 都可以从队列中获取任务出来。具体使用哪个方法根据 timed 来判断。
         take() 是没有就阻塞直到有,这里的 poll() 加了一个超时控制。
         其实熟悉阻塞队列就应该知道取出元素的方法有两个poll()和take(),
         前者是一个非阻塞方法,如果当前队列为空,直接返回,而take()是一个阻塞方法,
         即如果当前队列为空,阻塞线程,封装线程到AQS的条件变量的条件队列中,
          而上面的方法是一个介于二者之间的方法,语义是如果队为空,该方法会阻塞线程,
            但是有一个阻塞时间,如果到时见还没有被唤醒,就自动唤醒;
        看到这里就应该知道了,我们的线程在获取任务时,如果队列中已经没有任务,
       会在此处阻塞keepALiveTime的 时间,如果到时间都没有任务,
        就会return null(不是直接返回null,是最终),然后在runWorker()方法中,执行
        processWorkerExit(w, completedAbruptly);终止线程;
48        try {
49            Runnable r = timed ?
50                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
51                workQueue.take();
52            if (r != null)
53                return r;
54            timedOut = true;
55        } catch (InterruptedException retry) {
56            timedOut = false;
57        }
58    }
59}

从这个方法中貌似没有找到很明显的具体的时机,但是可以结合线程池执行任务时从 BlockingQueuegetTask() 和 这个方法注释中的第 4 点:

* 4. This worker timed out waiting for a task, and timed-out
2 *    workers are subject to termination (that is,
3 *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
4 *    both before and after the timed wait, and if the queue is
5 *    non-empty, this worker is not the last thread in the pool.

可以考虑去看看线程池的执行 Task 的方法,线程池执行 Task 本质就是通过 ThreadFactory 构建出来的 Thread 去 执行 Worker 中的 run() 方法:

1 /** Delegates main run loop to outer runWorker  */
2        public void run() {
3            runWorker(this);
4        }
 1 final void runWorker(Worker w) {
 2        ...
 3        ...
 4        try {
 5          while (task != null || (task = getTask()) != null) {
 6           ...
 7          }
 8        } finally {
 9            processWorkerExit(w, completedAbruptly);
10        }
11    }
 1/**
 2     * Performs cleanup and bookkeeping for a dying worker. Called
 3     * only from worker threads. Unless completedAbruptly is set,
 4     * assumes that workerCount has already been adjusted to account
 5     * for exit.  This method removes thread from worker set, and
 6     * possibly terminates the pool or replaces the worker if either
 7     * it exited due to user task exception or if fewer than
 8     * corePoolSize workers are running or queue is non-empty but
 9     * there are no workers.
10     *
11     * @param w the worker
12     * @param completedAbruptly if the worker died due to user exception
13     */
14    private void processWorkerExit(Worker w, boolean completedAbruptly) {
15        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
16            decrementWorkerCount();
17
18        final ReentrantLock mainLock = this.mainLock;
19        mainLock.lock();
20        try {
21            completedTaskCount += w.completedTasks;
22              //从 workers 中 remove 了一个 worker,即移除了一个工作线程
23            workers.remove(w);
24        } finally {
25            mainLock.unlock();
26        }
27
28        tryTerminate();
29
30        int c = ctl.get();
31        if (runStateLessThan(c, STOP)) {
32            if (!completedAbruptly) {
33                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
34                if (min == 0 && ! workQueue.isEmpty())
35                    min = 1;
36                if (workerCountOf(c) >= min)
37                    return; // replacement not needed
38            }
39            addWorker(null, false);
40        }
41    }

可以看到这个 processWorkerExit() 方法是在 runWorker() 方法的 finally 代码块中。即如果跳出了 runWorker() 方法的 while 循环,就会执行 processWorkerExit() 方法。跳出 while 循环的条件是 task 为 null 或者 getTask() 获取的结果为 null。在 processWorkerExit() 中,会从 workers 中移除 worker。说白了整个 Worker 的生命周期大致可以理解为:线程池干活了(execute() / submit()),然后就是正式干活了(runWorker()),使用 getTask() 获取任务(中间会有一系列的判断(corePoolSize 是否达到,任务队列是否满了,线程池是否达到了 maximumPoolSize,超时等),如果没有 task 了,就进行后期的扫尾工作并且从 workers 中移除 worker。

注意上面的 addWorker(null, false); 这个其实是个注意点优化点 当执行一开始自己写的Runnable command 的run方法如果执行异常 也会走上面的代码completedAbruptly=true 然后执行addWorker(null, false); 创建一个空的worker线程继续执行

重点:所以run里最好是把所有的异常捕获 而不要抛出 不然抛出异常 就会立刻销毁线程 然后创建一个新线程 线程池的作用就没有

其中,核心线程数也是可以被销毁的

int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
allowCoreThreadTimeOut 默认是false,但是如果设置成true,核心线程数也可以销毁
目录
相关文章
|
14天前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
14天前
|
Java 程序员 数据库
Java线程池让使用线程变得更加高效
使用一个线程需要经过创建、运行、销毁三大步骤,如果业务系统每个线程都要经历这个过程,那会带来过多不必要的资源消耗。线程池就是为了解决这个问题而生,需要时就从池中拿取,使用完毕就放回去,池化思想通过复用对象大大提高了系统的性能。线程池、数据库连接池、对象池等都采用了池化技术,下面我们就来学习下线程池的核心知识、面试重点~
54 5
Java线程池让使用线程变得更加高效
|
2天前
|
Java 数据库
【Java多线程】对线程池的理解并模拟实现线程池
【Java多线程】对线程池的理解并模拟实现线程池
10 1
|
4天前
|
消息中间件 监控 前端开发
面试官:核心线程数为0时,线程池如何执行?
线程池是 Java 中用于提升程序执行效率的主要手段,也是并发编程中的核心实现技术,并且它也被广泛的应用在日常项目的开发之中。那问题来了,如果把线程池中的核心线程数设置为 0 时,线程池是如何执行的? 要回答这个问题,我们首先要了解在正常情况下,线程池的执行流程,也就是说当有一个任务来了之后,线程池是如何运行的? ## 1.线程池的执行流程 正常情况下(核心线程数不为 0 的情况下)线程池的执行流程如下: 1. **判断核心线程数**:先判断当前工作线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务。 2. **判断任务队列**:如果大于核心线程数,则判断任务队列是否
11 1
面试官:核心线程数为0时,线程池如何执行?
|
12天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
|
16天前
|
监控 Java 调度
Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现
Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现
|
16天前
|
设计模式 Java
Java多线程实战-从零手搓一个简易线程池(三)线程工厂,核心线程与非核心线程逻辑实现
Java多线程实战-从零手搓一个简易线程池(三)线程工厂,核心线程与非核心线程逻辑实现
|
2天前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
9 0
|
2天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
11 1
|
2天前
|
存储 缓存 安全
【Java多线程】线程安全问题与解决方案
【Java多线程】线程安全问题与解决方案
9 1