线程池工作原理深入解析
目录
1. 线程正常的生命周期
2. 为什么要用线程池?
3. 线程池的核心原理
4. 怎样创建线程池?
5.线程池的代码实现
6. ThreadPoolExecutor 源码分析
7. ThreadPoolExecutor 工作原理展示(重点)
1. 线程正常的生命周期
我们知道,线程是有生命周期的,在中间不出现阻塞情况下,线程从一开始的新建状态——>就绪状态——>运行状态——>死亡状态。
2. 为什么要用线程池?
在我们实际开发业务需求时,往往会有很多的用户访问我们的业务,如果来一个用户就创建一个线程,用户结束又销毁线程,如此频繁往复,是非常消耗系统的性能的,因此在实际开发业务时,我们往往会使用线程池。
线程池的好处就是,我们可以在线程池中创建多个线程,当我们有业务需要用到线程时,它会自动到线程池中拿取已经存在线程而不会再去创建新的线程,当线程完成了相应业务后,它会把使用过的线程再还到线程池中而不会销毁它,等待下一次任务的执行,如此一来,就节省了线程的创建与销毁这一动作,提高了程序的运行效率。
3. 线程池的核心原理
(1)我们在初始创建线程池的时候,线程池是空的,当然我们也可以在创建线程池的时候就定义好线程池中有几个线程。
(2)当我们需要用到线程时,线程池会去创建新的线程对象,当任务执行完毕之后,它会把线程归还给线程池,下次再有业务需要用到线程时,不会去创建新的线程,直接复用线程池中已经存在的线程。
(3)如果有多个任务都需要使用线程,但是线程池中的线程都正在工作时,而且也无法在创建多余的线程,那么当钱的任务就会进行等待,什么时候有空余线程,任务就会再次开启。
4. 怎样创建线程池?
Java已经帮我们封装好了一个关于线程池的工具类,名叫 Executors,我们可以根据这个工具类去创建不同类型的线程池。这里我把 Executors 的源码中所有创建线程池的方式列举下来如下图
这些以 new 开头的都是线程池的构造方法,我们可以用它创建线程池。此外,线程池的类型也有很多种,但底层逻辑都差不多。这里我就随便拿两个当例子说一下。
(1)public static ExecutorsService newCachedThreadPool() ,可以创建一个没有线程上限的线程池,从严格意义上来说,它并不算是没有上限,它的上限就是 int 类型的上限,大约是21亿多,我们看它的源码即可得知,这里它的上显示 Integer 的最大值,但我们肯定不会去创建这么多的线程,服务器也承受不住的。另外先记住,该构造方法底层 new 了一个ThreadPoolExecutor,后面会说到。
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
(2)public static ExecutorsService newFixedThreadPool(int nThreads),可以创建一个有上限的线程池,上限就是我们传入的参数。
源码如下所示,这里也先记住,该构造方法 new 了一个ThreadPoolExecutor,后面会说到。
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
5.线程池的代码实现
测试线程池如何使用,我们先来定义一个线程类
// 实现 Runnable 接口
public class MyRunnable implements Runnable{// 重写 run 方法,循环5次@Overridepublic void run() {for (int i = 0; i < 5; i++) {
// 获取当前线程名称并打印System.out.println(Thread.currentThread().getName() + "------" + i);}}
}
然后再定义一个线程池,给它多添加几个任务
public static void main(String[] args) {// 创建一个有上限的线程池,上线设置为3ExecutorService pool = Executors.newFixedThreadPool(3);// 给线程池添加任务,多添加几个,超出它的上线
// 任务1pool.submit(new MyRunnable());
// 任务2pool.submit(new MyRunnable());
// 任务3pool.submit(new MyRunnable());
// 任务4pool.submit(new MyRunnable());
// 任务5pool.submit(new MyRunnable());
// 任务6pool.submit(new MyRunnable());}
运行 main 方法,得到如下结果
可以看到,在控制台中,尽管我们定义了6个线程任务,但线程池中却只有3个线程,分别是pool-1-thread-1,pool-1-thread-2,pool-1-thread-3,这也对应了我们创建线程池时标注的最大线程数量3,而且各位也可以发现,pool-1-thread-2 这个线程打印了两次for循环,其实下面还有线程pool-1-thread-1与线程pool-1-thread-3打印的另外两次for循环,从从我们也可以看出来,虽然我们定义了6个线程任务,但我们定义了一个线程池数量最大为3的线程池,当任务开始时,最先来的三个任务会直接使用线程池中的线程,当第四个第五个第六个任务来到时,它会在线程池的外边排队等待,当前三个任务有人把任务做完将线程归还给线程池后,其他排队等待的线程就可以取出线程池去空余的线程去完成自己的任务了!!!
6. ThreadPoolExecutor 源码分析
刚才我们举得那两个例子,特别用加粗字体说明了这两个线程池底层都是 new 了一个ThreadPoolExecutor,下面我们就来看一下它的源码。
在源码中,有好几个ThreadPoolExecutor 的构造方法,其中有一个最重要的,如下所示,该方法中一共有7个参数,我把它们代表的意义都列举出来。
对于 ThreadPoolExecutor 线程池来讲,我们重点需要理解的有两点,第一点就是这一大堆参数各自代表的含义,第二就是线程池的工作原理
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
int corePoolSize:代表线程池中的核心线程数量;
int maximumPoolSize:代表线程池中最大线程数量;
这里我先解释一下,该线程池中,核心线程永远不会被销毁,除非将线程池销毁,除了核心线程,我们还会有临时线程,就好比是临时工,如果我们线程池的任务过多,核心线程已经忙不过来了,就会有临时线程过来帮忙,当临时线程空闲了一段时间之后没有接到任务,它就又会被销毁。
临时线程 = 最大线程数量 - 核心线程数量
long keepAliveTime:临时线程允许存活的时间数值,如60,100,2000等任意long类型的数都可以;
TimeUnit unit:这个参数与上一个是紧密联系的,代表的是单位,可以为秒,分钟,小时;
如果上面 keepAliveTime 参数数值为60,TimeUnit 单位为Second,则表示表示空余线程可以空闲存活60秒;如果TimeUnit 单位为Minutes,则表示表示空余线程可以空闲存活60分钟,否则就会被销毁。
BlockingQueue<Runnable> workQueue:代表阻塞队列,如果我们的核心线程都在工作时,结果还有任务过来,就会暂时进入到这个阻塞队列中去,可以定义该队列的大小;
ThreadFactory threadFactory:表示我们定义的线程是从哪来的,即创建线程的方式;
RejectedExecutionHandler handler:表示当等待队列满了,临时线程也在工作状态的时候该如何处理,我们可以抛出异常,也可以拒绝服务等等一系列措施。
7. ThreadPoolExecutor 工作原理展示(重点)
刚才我大致说明了 ThreadPoolExecutor 的几个参数代表的意思,下面我来举个例子让大家更好的去理解它
如下所示
现在我定义一个线程池,核心线程数量定义为3,临时线程数量也定义为3,等待队列长度也定义为3,当我们来了10个任务需要线程池去完成时,我来一步步说明,前提是第十个任务来了第一个任务还没有完成,这里我简单描述,各位应该能看懂
(1)第一步,当来了前三个任务,而且是首次接受到任务时,线程池会去创建三个核心线程去处理任务123,如下图所示
(2)第二步,当来了任务456时,因为核心线程都处在工作状态,此时线程456 就会进入到等待队列(也叫阻塞队列)中去排队等待,而不是由临时线程去处理它们;如下图所示
(3)第三步,当来了任务789时,因为此时已经没有空余线程了,等待队列又是满的,此时线程池就会再去创建三个临时线程去处理这任务789;如下图所示
(4)第四步:当提交了第十个任务时,大家可以看到,此时核心线程在工作中,临时线程也在工作中,等待队列也满了,那么此时线程池就会触发拒绝策略;
(5)新来的任务就会被线程池拒绝服务,Java中一共有四种拒绝策略供我们选择,我们可以自行设置,如果不设置默认就是直接将新来的任务丢弃并抛出异常。
此外有一点需要注意,这四种任务拒绝策略都是静态内部类,我们直接通过类名.方法名调用即可。 一般情况下我们也不会去更改拒绝策略,所以其他三种各位同学可以作为了解哦!
相关文章:

线程池工作原理深入解析
目录 1. 线程正常的生命周期 2. 为什么要用线程池? 3. 线程池的核心原理 4. 怎样创建线程池? 5.线程池的代码实现 6. ThreadPoolExecutor 源码分析 7. ThreadPoolExecutor 工作原理展示(重点) 1. 线程正常的生命周期 我们知…...
chatGPT小白快速入门课程大纲
以下是关于ChatGPT的培训课程大纲,分为7部分,我们会在后续写一个系列的相关文章: 1. 介绍 ChatGPT是什么?ChatGPT是由谁开发的?ChatGPT是一个什么样的语言模型? 2. 功能与特点 ChatGPT可以做什么?ChatGPT有哪些特点?ChatGPT与传统语言模型的区别? 3. 使用方法 如何…...
网络编程——多路复用——epoll机制
理解 epoll:高效的 Linux I/O 多路复用机制 在网络编程中,处理多个并发连接是一个常见的挑战。传统的方式通常使用阻塞式 I/O 或者多线程/多进程来处理并发连接,但这些方法都存在一些性能和资源管理的问题。为了解决这些问题,Lin…...

chapter14:springboot与安全
Spring Boot与安全视频 Spring Security, shiro等安全框架。主要功能是”认证“和”授权“,或者说是访问控制。 认证(Authentication)是建立在一个声明主体的过程(一个主体一般指用户,设备或一些可以在你的应用程序中…...

Linux初识网络基础
目录 网络发展 认识“协议 ” 网络协议 OSI七层模型: TCP/IP五层(或四层)模型 网络传输基本流程 网络传输流程图: 数据包封装和封用 网络中的地址 认识IP地址: 认识MAC地址: 网络发展 1.独立…...
vue3+ts 动态导入多文件组件
1、在components文件夹中新建index.ts文件(components文件夹下为创建的组件) // index.ts const modules import.meta.globEager("./*.vue"); //参数为组件路径 let componentsOpts {}const getCaption (obj, str, z: boolean) > {let…...
补充122836356
ul {margin-bottom: 20px;& > li {margin-bottom: 0;} }等效于ul {margin-bottom: 20px; }ul > li {margin-bottom: 0; }CSS中的&代表的什么 如源码: ul{ margin-bottom: 20px; & >li { margin-bottom: 0; } } & 表示嵌套的上一级 这是s…...
记录 pl-table 表格头部文字抖动的问题
本文记录一个实际开发中 pl-table 的问题,项目比较老,vue还是2.x版本。pl-table 是基于 el-table 改造过来的表格展示组件,已经停止更新。 问题描述 当 data 内数据动态改变时,pl-table 的表头部分,列的文字会左右抖动…...

Vite 创建 Vue项目之后,eslint 错误提示的处理
使用 npm create vuelatest创建 vue 项目(TS)之后,出现了一些 eslint 错误提示,显然,不是代码真实的错误,而是提示搞错了。 vuejs/create-vue: 🛠️ The recommended way to start a Vite-pow…...

FFmpeg 硬编码VideoToolBox流程
介绍 FFmpeg已经提供对 VideoToolBox 的编解码支持;主要涉及到的文件有videotoolbox.c、videotoolbox.h、videotoolboxenc.c、ffmepg_videotoolbox.c。在编译 FFmpeg 源码时,想要支持VideoToolBox,在 configure 时,需要–enable-…...

恒盛策略:内盘是买入还是卖出?
内盘,又称成交明细,是指交易所实时发布的每一笔成交价格、交易时刻、成交量等具体数据。关于股民来说,每天重视内盘数据已成为必修课。可是,当看到内盘数据时,很多人都会困惑,到底内盘是买入还是卖出呢&…...

安装Lombok--Lombok的常用注解说明及使用方法
😀前言 本篇博文是关于Lombok的基本介绍和基本使用,希望能够帮助到您😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满…...

无涯教程-Perl - endpwent函数
描述 此功能告诉系统您不再希望使用getpwent从密码文件读取条目。在Windows下,使用Win32API::Net函数从域服务器获取信息。 语法 以下是此函数的简单语法- endpwent返回值 此函数不返回任何值。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perlwhile(($name, $pas…...
vue项目在body设置公共的背景前提下,区分首页背景图和其他页面背景图
1.需求:在vue项目已设置统一的body背景图的前提,单独给首页换一个背景图,然后其他页面背景图不变的临时需求 实现思路1:在首页home.vue中 在公共的style.css文件中写上两个背景样式(写在公共样式中是因为style.css比组件内部的先加载,避免页面出现后背景空白的问题) …...

测试人员该怎样写软件缺陷报告?
软件测试过程中,每个公司都制订了软件的缺陷处理流程,每个公司的软件缺陷处理流程不尽相同,但是它们遵循的最基本流程是一样的,都要经过提交、分配、确认、处理、复测、关闭等环节,如图1所示。 缺陷处理流程 关于图1所…...

【大数据】Flink 详解(二):核心篇 Ⅱ
Flink 详解(二):核心篇 Ⅱ 22、刚才提到 State,那你简单说一下什么是 State。 在 Flink 中,状态 被称作 state,是用来保存中间的计算结果或者缓存数据。根据状态是否需要保存中间结果,分为 无状…...
一孩半政策
一) 一孩半,又称独女户二胎,即中国大陆部分农村的一项计划生育政策,第一胎是女孩的夫妻可生育第二个子女。试问这个政策会不会影响男女平衡。 二)如果生女孩一直生,直到生男孩停止,试问会不会…...

如何在 Spring Boot 中集成日志框架 SLF4J、Log4j
文章目录 具体步骤附录 笔者的操作环境: Spring Cloud Alibaba:2022.0.0.0-RC2 Spring Cloud:2022.0.0 Spring Boot:3.0.2 Nacos 2.2.3 Maven 3.8.3 JDK 17.0.7 IntelliJ IDEA 2022.3.1 (Ultimate Edition) 具体步骤 因为 …...

如何在Linux布置nginx(附带Nginx基本操作步骤)
文章目录 前言一、下载环境依赖二、下载nginx安装包三、具体操作流程总结 前言 提示:下述操作步骤适合内网服务器、局域网服务器和公网服务器。 不足之处欢迎交流指正,不喜勿喷。 一、下载环境依赖 yum -y install gcc zlib zlib-devel pcre-devel ope…...

Xcode升级导致关联库报错
想办法找到对应的库 然后到 Build Phases -- LinkBinary With Libraries中点击,选择对应的framework即可,就像我工程的报错 Undefined symbol: _OBJC_CLASS_$_ADClient _OBJC_CLASS_$_ASIdentifierManager 缺失的库是AdSupport.framework 添加后再次编…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...