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

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


首先要知道在线程池中空余线程被回收的条件:当线程池中的线程数量大于 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,核心线程数也可以销毁
目录
相关文章
|
22天前
|
Java 数据库连接 调度
面试题:用过线程池吗?如何自定义线程池?线程池的参数?
字节跳动面试题:用过线程池吗?如何自定义线程池?线程池的参数?
27 0
|
1天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
|
5天前
|
监控 Java 调度
Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现
Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现
|
5天前
|
Java 测试技术
Java多线程实战-从零手搓一个简易线程池(二)线程池实现与拒绝策略接口定义
Java多线程实战-从零手搓一个简易线程池(二)线程池实现与拒绝策略接口定义
|
5天前
|
存储 安全 Java
Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列
Java多线程实战-从零手搓一个简易线程池(一)定义任务等待队列
|
6天前
|
安全 算法 Java
JavaSE&多线程&线程池
JavaSE&多线程&线程池
127 7
|
12天前
|
设计模式 安全 Java
【JavaEE多线程】从单例模式到线程池的深入探索
【JavaEE多线程】从单例模式到线程池的深入探索
19 2
|
21天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
1天前
|
缓存 安全 Java
多线程--深入探究多线程的重点,难点以及常考点线程安全问题
多线程--深入探究多线程的重点,难点以及常考点线程安全问题
|
2天前
|
数据采集 安全 Java
Python的多线程,守护线程,线程安全
Python的多线程,守护线程,线程安全