线程池创建、执行、销毁的原理解析

news2024/11/26 16:36:59

目录

    • 线程池的执行原理
    • 线程执行
    • 参考:

线程池的执行原理

假设最大核心数是2,非核心线程数为1,队列长度是3
在这里插入图片描述
来第一个任务的时候,没有工作线程在工作,需要创建一个
在这里插入图片描述
在这里插入图片描述
来第二个任务的时候,发现当前核心线程数小于最大核心线程数,所以继续创建线程来处理任务
在这里插入图片描述
在这里插入图片描述
当来第三个任务的时候,发现当前核心线程数已经等于最大核心线程数了,所以把新来的任务放到taskQueue中
在这里插入图片描述

后面来第四个、第五个任务也会放在taskQueue中
在这里插入图片描述

当来第六个任务的时候,发现taskQueue已经满了,所以会创建一个非核心线程来处理任务
在这里插入图片描述
当来第七个任务的时候,因为线程数量到最大限度了,taskQueue也满了,所以就会走拒绝策略,把其中一个任务给抛弃掉,具体抛弃哪个需要根据选择的拒绝策略来定。

创建线程这里需要考虑并发的问题,即多个任务同时过来了,需要串行创建线程,否则,可能会导致超卖的情况(即创建的线程超过了最大线程数),具体是通过CAS乐观锁实现,代码解释如下:

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();//获取当前线程池的控制状态。
            int rs = runStateOf(c);//获取当前线程池的运行状态。

            // Check if queue empty only if necessary.
            //下面这个条件判断用于判断线程池是否处于关闭状态,并且任务队列不为空。如果线程池的状态大于等于SHUTDOWN,并且不满足线程池状态为SHUTDOWN、首个任务为null且任务队列为空的条件,则返回false。这个判断是为了确保在线程池关闭时,不再添加新的工作线程。
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);//获取当前线程池中的工作线程数量。
                //这个条件判断用于判断工作线程数量是否达到上限。如果工作线程数量大于等于CAPACITY(工作线程数量的上限)或者大于等于核心线程数(如果core为true)或最大线程数(如果core为false),则返回false。这个判断是为了确保工作线程数量不超过线程池的限制。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                    //尝试通过CAS(比较并交换)操作增加工作线程数量。如果成功增加工作线程数量,则跳出循环,继续执行后续逻辑。
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // 重新读取当前线程池的控制状态。
                //再次判断线程池的运行状态是否发生了变化。如果运行状态发生了变化,则继续重试内部循环。这个判断是为了处理在CAS操作过程中,线程池的状态发生了变化的情况。
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        ...创建线程的逻辑忽略...
}

线程执行

花开两朵,各表一枝。
上面说了任务来了之后,是怎么创建线程的,又是怎么暂存任务的。这一节介绍一下线程是怎么执行任务的,以及在不用的时候,线程怎么被销毁或者保活。
线程池创建线程并调了thread的start方法之后,该线程会走到下面的runWorker方法。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

当一个线程执行完就会自动退出,但是我们知道线程池中的核心线程会一直存活着,想要一直存活,不退出程序就可以了,即循环,从上面的代码也可以看出来确实是这样的。但是还有一个疑问,核心线程是一直存活的,但是非核心线程在一定情况是会销毁的,他们用的是一套代码逻辑,该怎么实现呢?关键点就在getTask这个方法中。

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?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//销毁线程需要满足这两个条件:1. (允许核心线程销毁 || 线程数大于核心线程数)&& 达到了销毁时间;2. 任务队列中没有任务了
            if ((wc > maximumPoolSize || (timed && timedOut)) 
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                // 这里返回了null,外层方法就跳出了while循环,从而结束该线程
                    return null;
                continue;
            }

            try {
            // 超时时间就是在这里设置的,如果允许超时销毁,那么就用poll进行拉取任务,超过了keepAliveTime就返回null。take是阻塞性等待
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

从上面的解析可以看出,如果队列中没有任务时,小于核心数的线程(核心线程数不销毁的情况下)会一直阻塞在获取任务的方法,直到返回任务。(判断阻塞时并没有核心线程和非核心线程的概念,只要保证创建出来的线程销毁到符合预期数量就ok)。而且执行完后 会继续循环执行getTask的逻辑,不断的处理任务。

参考:

https://blog.csdn.net/lbh199466/article/details/102700780

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1193130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 UTF-8与GBK编码互转,一个头文件搞定!不使用第三方库,纯C语言跨平台

背景: 项目需要在mips32架构的freertos系统上将输入的汉字、标点符号、数字、字母等转成机内码输出,第一个想到的办法是移植第三方库进行编码转换,网上搜了一圈libiconv、ICU库都可以实现,但是在我的项目上移植编译各种报错、非常…

【漏洞复现】jumpserver任意密码重置

jumpserver任意密码重置漏洞-CVE-2023-42820 jumpserver jumpserver是FIT2CLOUD飞致云旗下的开源堡垒机。 环境搭建 这里用的是vulhub靶场 进入 jumpserver 的目录 修改配置文件 config.env 里面的 DOMAINS 参数为kali的地址 运行环境,第一次运行的话会拉取文件…

rocksdb 中 db_bench 的使用方法

硬件要求 硬件要求如表1所示。 表1 硬件要求 项目 说明 CPU 12 * AMD Ryzen 5 5500U with Radeon Graphics 内存 DDR4 磁盘 HDD 软件要求 软件要求如表2所示。 表2 软件要求 项目 版本 说明 下载地址 CentOS 7.6 操作系统。 Download kernel 4.14.0 内核。…

射频功率放大器有哪些用途

射频功率放大器是一种专用于放大射频信号的设备,它在现代通信、广播、雷达、无线电频谱监测和科学研究等领域中发挥着重要的作用。射频功率放大器能够将输入的低功率射频信号放大为较高功率的信号,以满足各种应用场景对信号传输距离、质量和稳定性的要求…

基于VPLC711的曲面外观检测XYR运动控制解决方案

市场应用背景 随着消费升级,产品形态正在朝着多样性和精细化方向迅速发展。这导致了对于复杂曲面轨迹加工的需求,包括外观检测、打磨抛光和点胶工艺控制,要求更高的精密度。企业必须主动满足市场需求,不断改进工艺,以…

Fabric: 使用InvokeChaincode实现跨通道数据访问

因为工作中遇到一些问题考虑使用Fabric的跨通道链码调用方法InvokeChaincode()来解决,这篇文章主要是记录以下在Fabric测试网络中InvokeChaincode()的使用过程及遇到的问题。 1 前期准备 1.1 认识InvokeChaincode InvokeChaincode的作用是调用指定的链码。而被调用…

数字滤波器分析---相位响应

数字滤波器分析---相位响应 MATLAB 函数可用于提取滤波器的相位响应。在给定频率响应的情况下,函数 abs 返回幅值,angle 返回以弧度为单位的相位角。要使用 fvtool 查看巴特沃斯滤波器的幅值和相位,请使用: d designfilt(lowpa…

Linux应用开发基础知识——Framebuffer 应用编程(四)

前言: 在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。Frame 是帧的意 思,buffer 是缓冲的意思,这意味着 Framebuffer 就是一块内存,里面保存着 一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值,假设…

2023年9月少儿编程 中国电子学会图形化编程等级考试Scratch编程二级真题解析(选择题)

2023年9月scratch编程等级考试二级真题 选择题(共25题,每题2分,共50分) 1、点击绿旗,运行程序后,舞台上的图形是 A、画笔粗细为4的三角形 B、画笔粗细为5的六边形 C、画笔粗细为4的六角形 D、画笔粗细为5的三角形 答案:D 考点分析:考查积木综合使用,重点考查画笔…

100 寻找重复数

寻找重复数 题解1 二分法题解2 快慢指针(同环形链表2(ab)(ab)kL) 给定一个包含 n 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 ,返…

LDR6023AQ-PDHUB最简外围成本低搂到底就是干

USB-C PD协议里,SRC和SNK双方之间通过CC通信来协商请求确定充电功率及数据传输速率。当个设备需要充电时,它会发送消息去给适配器请求充电,此时充电器会回应设备的请求,并告知其可提供的档位功率,设备端会根据适配器端…

@JSONField或@JsonProperty注解使用

一、需求 使用JSONField或JsonProperty注解,来解决bean与json字段不一致问题,或者字段定义不符合前端所需要的标准,最近在项目中发现实体类属性中,同时使用了JSONField和JsonProperty注解,用于重新声明属性key。有时候…

数字滤波器分析---频率响应

数字滤波器分析---频率响应 幅值、相位、冲激和阶跃响应、相位和群延迟、零极点分析。 分析滤波器的频域和时域响应。可视化复平面中的滤波器极点和零点。 频率响应 数字域 freqz 使用基于 FFT 的算法来计算数字滤波器的 Z 变换频率响应。具体来说,语句 [h,w]…

Android jetpack compose 组件学习

官网地址:https://developer.android.com/jetpack/compose/tutorial?hlzh-cn 一、写一个 hello world 在New Project的时候选择Photo and Tablet里的Empty Compose Activity,如下所示: 打开这个project之后,可以看到MainActivit…

重新定义客户服务 UniPro Mailhandler 彻底改变团队处理请求模式

现代企业管理越精细越专业,涉及到的跨团队、跨组织之间的高效协作程度越高。如果没有趁手的协作工具加持,协作过程中必然会出现一些问题: 信息查找低效,例如找人、找部门,不清楚任务分配该找谁; 信息传达…

leetcode刷题 - SQL - 中等

1. 176. 第二高的薪水 筛选出第二大 查询并返回 Employee 表中第二高的薪水 。如果不存在第二高的薪水,查询应该返回 null(Pandas 则返回 None) 。查询结果如下例所示。 666中等的第一题就上强度 强行解法 select max(salary) as SecondHighestSalary from Emp…

模型剪枝算法——L1正则化BN层的γ因子

ICCV在2017年刊登了一篇经典论文《 Learning Efficient Convolutional Networks through Network Slimming》。在神经网络的卷积操作之后会得到多个特征图,通过策略突出重要的特征达到对网络瘦身的目的。在该论文中使用的剪枝策略就是稀疏化BN层中的缩放因子 。 Bat…

NSS [鹏城杯 2022]压缩包

NSS [鹏城杯 2022]压缩包 考点&#xff1a;条件竞争/逻辑漏洞&#xff08;解压失败不删除已经解压文件&#xff09; 参考&#xff1a;回忆phpcms头像上传漏洞以及后续影响 | 离别歌 (leavesongs.com) 源码有点小多 <?php highlight_file(__FILE__);function removedir($…

【Python】 Python 使用 Pillow 处理图像:几何变换

Python 使用 Pillow 处理图像&#xff1a;几何变换 pillow库操作切片、旋转、滤镜、输出文字、调色板等功能一应俱全。 1. 几何变换 Image 包含调整图像大小 resize() 和旋转 rotate() 的方法。前者采用元组给出新的大小&#xff0c;后者采用逆时针方向的角度。 调整大小并…

视频批量剪辑:AI智剪入门,轻松掌握智能剪辑技巧

在数字媒体时代&#xff0c;视频剪辑已经成为一项必备的技能。无论是为了工作需要&#xff0c;还是为了在社交媒体上分享生活&#xff0c;掌握视频剪辑技巧都能为我们的生活和工作带来很多便利。然而&#xff0c;对于初学者来说&#xff0c;视频剪辑可能是一项艰巨的任务。现在…