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

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


首先要知道在线程池中空余线程被回收的条件:当线程池中的线程数量大于 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,核心线程数也可以销毁
目录
相关文章
|
7月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
460 2
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
673 60
【Java并发】【线程池】带你从0-1入门线程池
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
Java
线程池是什么?线程池在实际工作中的应用
总的来说,线程池是一种有效的多线程处理方式,它可以提高系统的性能和稳定性。在实际工作中,我们需要根据任务的特性和系统的硬件能力来合理设置线程池的大小,以达到最佳的效果。
320 18
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
Java
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
464 4
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
718 2
|
10月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
435 83
|
7月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
288 6
|
12月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
418 0

热门文章

最新文章