【多线程】线程池(上)
文章目录
- 线程池基本概念
- 线程池的优点
- 线程池的特点
- 创建线程池
- 自定义线程池
- 线程池的工作原理
- 线程池源码分析
- 内置线程池
- `newFixedThreadPool`
- `SingleThreadExecutor`
- `newCachedThreadPool`
- `ScheduledThreadPool`
- 线程池的核心线程是否会被回收?
- 拒绝策略
- ThreadPoolExecutor.AbortPolicy
- ThreadPoolExecutor.CallerRunsPolicy
- ThreadPoolEcecutor.DiscardPolicy
- ThreadPoolExecutor.DiscardOldestPolicy
线程池基本概念
在讲解线程池之前,我们应该先来了解池化技术
池化技术:
池化技术是一种用于管理和复用线程的技术,旨在提高系统的性能和资源利用率。线程池通过预先创建一组线程来处理任务,从而避免频繁地创建和销毁线程带来的开销。
池化技术不仅在线程池中体现,还在数据库连接池,HTTP连接池,字符串常量池中均有体现.
如果不使用池化技术,我们创建一个线程的步骤:
- 手动创建线程对象
- 执行任务
- 执行完毕,释放线程对象
此处,每一次创建线程资源和释放线程资源,均会消耗系统资源,而如果我们采用线程池的方式,由线程池统一创建和管理线程资源,就可以降低系统资源的消耗,提高对资源的利用率.
所以线程池,提供一种限制和管理资源的方式.
线程池的优点
- 降低资源消耗:通过重复利用已经创建的线程降低线程创建和销毁造成的资源消耗.
- 提高程序的响应速度:当任务到达时,任务可以不需要等待线程的创建就可以立即执行
- 便于统一管理线程对象:线程池可以统一进行线程分配
- 可以控制最大的并发数(线程池会限制最大的线程对象的个数,这样可以限制并发量,避免无限制的创建线程资源,造成系统资源的损耗)
线程池的特点
- 线程复用
- 控制最大并发数
- 管理线程
创建线程池
自定义线程池
对于线程池的管理和使用,我们使用在java.util.concurrent下的ThreadPoolExecutor线程池工具类
来进行线程池的创建和管理
public class ThreadPoolExecutor extends AbstractExecutorService{//成员属性:private volatile int corePoolSize;//核心线程数private volatile int maximumPoolSize;//最大线程数private volatile long keepAliveTime;//非核心线程的最大存活时间private final BlockingQueue<Runnable> workQueue;//工作队列private volatile ThreadFactory threadFactory;//线程工厂private volatile RejectedExecutionHandler handler;//拒绝策略//...//线程池的构造方法:public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)//... }
参数:
corePoolSize:任务队列未达到队列最大容量时,最大可以同时运行的线程数量maximumPoolSize:任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数workQueue:新任务来的时候,会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中keepAliveTime:当线程池中的线程数量大于corePoolSize,即有非核心线程(线程池中核心线程以外的线程)时,这些非核心线程空闲后不会立即被销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁Unit:keepAliveTime参数的时间单位threadFactory:executor创建新线程时会用到headler:拒绝策略(后续详解))
接下来我们采用图解的方式,来系统讲解一下这里各种参数具体使用,并了解线程池的工作原理.
线程池的工作原理
我们下述线程池原理的讲解,以一个corePoolSize(核心线程数)为9,maximumPoolSize(最大线程数)为13,workQueue(工作队列)大小为5,keepAliveTime(非核心线程存活时间)为10s来举例.
-
当此时的线程池中已被创建的线程<核心线程数,线程池会创建新的线程执行任务

-
此时的线程池中已创建的线程池数==核心线程数

-
此时新的任务进入线程池中,会先进入阻塞队列中进行等待,核心线程中如果存在空闲的线程,就会去阻塞队列中取任务进行执行


-
如果工作阻塞队列已满,核心线程也在执行任务,但最大线程数>已创建线程数,那么进入的任务后,线程池会创建新的线程,直到最大线程数==已创建线程数.

-
在线程池中工作阻塞队列已满,且已创建线程数==最大线程数时,此时再来任务时,线程池就会触发拒绝策略.

-
当线程池中的额外创建的线程空闲下来了,是否马上就会销毁呢?
并不是,线程池会根据设定的keepAliveTime时间来判断什么时候销毁这些线程.
那么是不是一定要销毁原本创建的额外创建的线程呢?
并不是,线程池只需要维护核心线程数和最大线程数的数量即可,所以线程池中的线程并没有一个明确的身份

其中的几个线程空闲时间超过了keepAliveTime时间后:

以上就是线程池的工作原理和参数的理解.线程池源码分析
我们从
execute入手,了解线程池的源码中是如何实现上述过程的.public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}
接下来我们来观察一下addWorker方法里是如何创建一个新的线程的
private boolean addWorker(Runnable firstTask, boolean core) {
//......Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;

我们发现,最终是调用了ThreadFactory参数来创建线程的.
这里还需要注意
Worker类是实现了Runnable接口的类,所以Worker本身也是一个Runnable,所以创建线程是,调用的this.thread=getThreadFactory().newThread(this)传入的this对象就是Worker,所以线程执行的run()方法,是Worker内部的run()方法,而调用内部的run()方法后,在调用runWorker(this)方法,我们来看一看runWorker()方法中的内容:
我们看到,这里传入了参数this对象,其中this中的成员属性firstTask就是我们传入的FirstTask
在这里我们看到了task.run()的身影,说明此处执行了我们传入的任务,并且在执行任务之前和执行任务之后还可以调用相关的方法
接下来我们来观察一下是如何将任务加入到队列中的.

底层的工作队列是阻塞队列,任务会通过offer方法加入到队列中,此时如果队列中没有任务,线程来队列中获取任务时,就会阻塞在这个位置,直到有任务加入到队列中.所以使用阻塞队列,也实现了核心线程的保活
内置线程池
除了使用JUC包下的ThreadPoolExecutor来创建线程,我们还可以使用JUC包下的工具类Executor来创建具有一定固定功能的线程池
建立一个任务(后面的几个实验也是使用此任务执行)
class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}}
}
newFixedThreadPool
newFixedThreadPool:创建固定线程数量的线程池
使用newFixedThreadPool创建一个线程数为4的线程池
public class test1 {public static void main(String[] args) throws InterruptedException {ExecutorService threadPool= Executors.newFixedThreadPool(4);for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}TimeUnit.SECONDS.sleep(15);threadPool.shutdown();System.out.println(threadPool.isShutdown());}
}
运行结果:

我们发现,这里会重复利用4个线程,不会创建多于4的线程.
SingleThreadExecutor
SingleThreadExecutor:只有一个线程的线程池
public class test2 {public static void main(String[] args) throws InterruptedException {ExecutorService threadPool= Executors.newSingleThreadExecutor();for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}TimeUnit.SECONDS.sleep(15);threadPool.shutdown();}
}
运行结果:

newCachedThreadPool
newCachedThreadPool:可以根据实际情况动态调整线程数量的线程池
public class test3 {public static void main(String[] args) {ExecutorService threadPool= Executors.newCachedThreadPool();for(int i =0;i<10;i++){threadPool.execute(new MyRunnable());}threadPool.shutdown();}
}
运行结果:

我们发现,此时线程池选择创建十个线程来执行这十个任务
ScheduledThreadPool
ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池
线程池的核心线程是否会被回收?
ThreadPoolExecutor默认不会回收核心线程,即使他们已经空闲了.这是为了减少创建线程的开销,因为核心线程通常是要长期保持活跃的.
但如果线程池是用于周期性使用的场景(也就是线程池忙碌-空闲存在周期性),可以使用allowCoreThreadTimeOut(boolean value)方法的参数设置为true,这样就会回收空闲的核心线程了
拒绝策略
我们在阅读源码的过程中,已经关注到了拒绝策略这个问题.也就是说:当线程池已经达到最大并发量的时候,我们需要对新来的任务进行"拒绝".那么线程池中的拒绝策略有哪些呢?
ThreadPoolExecutor.AbortPolicy
抛出RejectedExecutionException来直接拒绝新任务的处理(这里线程池默认的拒绝策略)
public class test5 {public static void main(String[] args) {BlockingQueue workQueue=new ArrayBlockingQueue(1);ExecutorService threadPool= new ThreadPoolExecutor(1,2,1L,TimeUnit.SECONDS,workQueue);for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}threadPool.shutdown();}
}
分析代码:
此处的场景中,线程池的最大并发量是3,但我们需要执行十个任务,那么一定会触发默认的拒绝策略.
运行结果:
我们看到,这里执行了三个任务,就在主线程中抛出了RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy
这个是使用调用execute方法的线程来运行被拒绝的任务,如果执行execute方法的线程已关闭,则会丢弃该任务.
public static void main(String[] args) { BlockingQueue workQueue=new ArrayBlockingQueue(1); ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,2,1L,TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.CallerRunsPolicy()); for(int i=0;i<10;i++){ threadPool.execute(new MyRunnable()); } threadPool.shutdown(); }
}
分析代码:
这里我们创建了一个最大并发数为3的线程池,并且将拒绝策略设定为ThreadPoolExecutor.CallerRunsPolicy,此时我们在运行过程中,肯定会存在线程达到了最大并发数,触发拒绝策略的情况.而ThreadPoolExecutor.CallerRunsPolicy会将需要执行的任务退回给调用者(即调用execute方法的线程,在此处是main线程),由调用者线程来执行该任务,这样就能够保证所有任务都不会被抛弃,而是被执行
运行结果:

我们看到这里mian方法执行了任务
ThreadPoolEcecutor.DiscardPolicy
不处理新任务,直接丢掉
public class test6 {public static void main(String[] args) {BlockingQueue workQueue=new ArrayBlockingQueue(1);ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,2,1L, TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.DiscardPolicy());for(int i=0;i<10;i++){threadPool.execute(new MyRunnable());}threadPool.shutdown();}
}
运行结果:

这里我们发现,线程池只执行了三个任务,就结束了,并没有抛出异常或者将任务退回给execute的调用者.
ThreadPoolExecutor.DiscardOldestPolicy
此策略将丢弃掉阻塞队列中最早的任务.
我们来进行一个实验:
public class test7 {private static int i=1;static class MyRunnable_1 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务1");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}static class MyRunnable_2 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务2");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}static class MyRunnable_3 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行任务3");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {BlockingQueue workQueue=new ArrayBlockingQueue(1);ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,1,1L, TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.DiscardOldestPolicy());threadPool.execute(new MyRunnable_1());threadPool.execute(new MyRunnable_2());threadPool.execute(new MyRunnable_3());threadPool.shutdown();}
}
分析代码:
这里我们创建了三个任务,分别是MyRunnable_1,MyRunnable_2,MyRunnable_3,我们设定线程池的最大并发数为2,即最多线程数为1,阻塞队列为1,这样三个任务执行的时候,首先会执行第一个任务,而第一个任务需要10s,第二个任务进入阻塞队列中进行等待,第三个任务进入触发拒绝策略,所以会将第二个任务抛弃掉.所以代码最终呈现的结果应该是:任务1,3被执行,任务2被抛弃
运行结果:
相关文章:
【多线程】线程池(上)
文章目录 线程池基本概念线程池的优点线程池的特点 创建线程池自定义线程池线程池的工作原理线程池源码分析内置线程池newFixedThreadPoolSingleThreadExecutornewCachedThreadPoolScheduledThreadPool 线程池的核心线程是否会被回收?拒绝策略ThreadPoolExecutor.AbortPolicyT…...
ansible 语句+jinjia2+roles
文章目录 1、when语句1、判断表达式1、比较运算符2、逻辑运算符3、根据rc的返回值判断task任务是否执行成功5、通过条件判断路径是否存在6、in 2、when和其他关键字1、block关键字2、rescue关键字3、always关键字 3、ansible循环语句1、基于列表循环(whith_items)2、基于字典循…...
【Docker项目实战】使用Docker部署HumHub社交网络平台
【Docker项目实战】使用Docker部署HumHub社交网络平台 一、HumHub介绍1.1 HumHub简介1.2 HumHub特点1.3 主要使用场景二、本次实践规划2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本四、下载HumHub镜…...
“医者仁术”再进化,AI让乳腺癌筛查迎难而上
世卫组织最新数据显示,我国肿瘤疾病仍然呈上升趋势,肿瘤防控形势依然比较严峻。尤其是像乳腺癌等发病率较高的疾病,早诊断和早治疗意义重大,能够有效降低病死率。 另一方面,中国地域广阔且发展不平衡,各地…...
安卓流式布局实现记录
效果图: 1、导入第三方控件 implementation com.google.android:flexbox:1.1.0 2、布局中使用 <com.google.android.flexbox.FlexboxLayoutandroid:id"id/baggageFl"android:layout_width"match_parent"android:layout_height"wrap_co…...
-bash gcc command not found解决方案(CentOS操作系统)
以 CentOS7 为例,执行以下语句 : yum install gcc如果下载不成功,并且网络没有问题。 执行以下语句 : cp -r /etc/yum.repos.d /etc/yum.repos.d.bakrm -f /etc/yum.repos.d/*.repocurl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.…...
(二)Python输入输出函数
一、输入函数 input函数:用户输入的数据,以字符串形式返回;若需数值类型,则进行类型转换。 xinput("请入你喜欢的蔬菜:") print(x) 二、输出函数 print函数 输出单一数值 x666 print(x) 输出混合类型…...
从调用NCCL到深入NCCL源码
本小白目前研究GPU多卡互连的方案,主要参考NCCL和RCCL进行学习,如有错误,请及时指正! 内容还在整理中,近期不断更新!! 背景介绍 在大模型高性能计算时会需要用到多卡(GPU…...
深入理解Transformer的笔记记录(精简版本)NNLM → Word2Vec
文章的整体介绍顺序为: NNLM → Word2Vec → Seq2Seq → Seq2Seq with Attention → Transformer → Elmo → GPT → BERT 自然语言处理相关任务中要将自然语言交给机器学习中的算法来处理,通常需要将语言数学化,因为计算机机器只认数学符号…...
优选算法第一讲:双指针模块
优选算法第一讲:双指针模块 1.移动零2.复写零3.快乐数4.盛最多水的容器5.有效三角形的个数6.查找总价格为目标值的两个商品7.三数之和8.四数之和 1.移动零 链接: 移动零 下面是一个画图,其中,绿色部分标出的是重点: 代码实现&am…...
智能优化算法-水循环优化算法(WCA)(附源码)
目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 水循环优化算法 (Water Cycle Algorithm, WCA) 是一种基于自然界水循环过程的元启发式优化算法,由Shah-Hosseini于2012年提出。WCA通过模拟水滴在河流、湖泊和海洋中的流动过程,以及蒸发…...
基于SpringBoot的个性化健康建议平台
1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理基于智能推荐的卫生健康系统的相关信息成为…...
Mapsui绘制WKT的示例
步骤 创建.NET Framework4.8的WPF应用在NuGet中安装Mapsui.Wpf 4.1.7添加命名空间和组件 <Window x:Class"TestMapsui.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winf…...
Modbus TCP 西门子PLC指令以太口地址配置以及 Poll Slave调试软件地址配置
1前言 本篇文章讲了 Modbus TCP通讯中的一些以太网端口配置和遇到的一些问题, 都是肝货自己测试的QAQ。 2西门子 SERVER 指令 该指令是让外界设备主动连接此PLC被动连接, 所以这里应该填 外界设备的IP地址。 这边 我因为是电脑的Modbus Poll 主机来…...
MySQL表的基本查询上
1,创建表 前面基础的文章已经讲了很多啦,直接上操作: 非常简单!下一个! 2,插入数据 1,全列插入 前面也说很多了,直接上操作: 以上插入和全列插入类似,全列…...
MySQL中什么情况下类型转换会导致索引失效
文章目录 1. 问题引入2. 准备工作3. 案例分析3.1 正常情况3.2 发生了隐式类型转换的情况 4. MySQL隐式类型转换的规则4.1 案例引入4.2 MySQL 中隐式类型转换的规则4.3 验证 MySQL 隐式类型转换的规则 5. 总结 如果对 MySQL 索引不了解,可以看一下我的另一篇博文&…...
数据治理的意义
数据治理是一套管理数据资产的流程、策略、规则和控制措施,旨在确保数据的质量、安全性、可用性和合规性。数据治理的目标通常包括但不限于以下几点: 1. **提高数据质量**:确保数据的准确性、一致性、完整性和可靠性。 2. **确保数据安全**…...
快手游戏服务端C++开发一面-面经总结
1、tcp的重传机制有哪几种?具体描述一下 最基本的超时重传 超过时间就会重传 三个重复ACK 快速重传 减少等待超时、 接收方可以发送选择性确认 不用重传整段 乱序到达 可以通知哪些丢失 重复数据重传 2、override和final? override可写可不写 写出来就…...
git的学习使用(认识工作区,暂存区,版本区。添加文件的方法)
学习目标: 学习使用git,并且熟悉git的使用 学习内容: 必备环境:xshell,Ubuntu云服务器 如下: 搭建 git 环境认识工作区、暂存区、版本区git基本操作之添加文件(1):gi…...
Series数据去重
目录 准备数据 Series数据去重 DataFrame数据和Series数据去重对比 在pandas中,Series.drop_duplicates(keep, inplace)方法用于删除Series对象中的重复值。 keep: 决定保留哪些重复值。可以取以下三个值之一: first(默认值&…...
热键冲突解决:从检测到修复的完整指南
热键冲突解决:从检测到修复的完整指南 【免费下载链接】hotkey-detective A small program for investigating stolen hotkeys under Windows 8 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detective 在日常电脑使用中,我们经常会遇到这…...
ReplaceItems.jsx:基于智能匹配引擎的Illustrator对象替换解决方案
ReplaceItems.jsx:基于智能匹配引擎的Illustrator对象替换解决方案 【免费下载链接】illustrator-scripts Adobe Illustrator scripts 项目地址: https://gitcode.com/gh_mirrors/il/illustrator-scripts 副标题:面向专业设计师的批量元素管理工具…...
程序实现环境温度对传感器的误差补偿,不同温度下测量精度一致,颠覆温漂难题。
无论你是做工业传感还是消费电子,只要你测物理量(电压、电流、压力、流量),温度就是精度的头号杀手。今天我们用 Python 打造一套自适应温度补偿系统,让仪器在不同温度下“不忘初心”。一、 实际应用场景描述 (Scenari…...
基于SpringBoot的CLAP音频分类服务开发实战
基于SpringBoot的CLAP音频分类服务开发实战 1. 项目背景与价值 音频分类在实际业务中有着广泛的应用场景,比如内容审核、智能家居、媒体分析等。传统的音频分类方案通常需要大量标注数据来训练专用模型,这在很多实际场景中成本高昂且不够灵活。 CLAP&…...
Linux系统管理必备:常用命令在Phi-3-vision模型部署与运维中的应用
Linux系统管理必备:常用命令在Phi-3-vision模型部署与运维中的应用 1. 前言:为什么需要掌握这些命令 部署和管理AI模型服务时,熟练使用Linux命令就像拥有了一把瑞士军刀。特别是对于Phi-3-vision这样的视觉大模型,从查看日志到监…...
XHS-Downloader:构建高效采集流程的无水印内容批量管理方案
XHS-Downloader:构建高效采集流程的无水印内容批量管理方案 【免费下载链接】XHS-Downloader 小红书(XiaoHongShu、RedNote)链接提取/作品采集工具:提取账号发布、收藏、点赞、专辑作品链接;提取搜索结果作品、用户链接…...
如何为华硕笔记本安装轻量级性能控制工具:G-Helper完整指南
如何为华硕笔记本安装轻量级性能控制工具:G-Helper完整指南 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, Stri…...
Copilot 插入广告引担忧,AI 工具商业化边界受考
Copilot 拉取请求中惊现广告插入团队成员使用 Copilot 纠正拉取请求(PR)中的拼写错误时,出现了令人意想不到的情况。Copilot 不仅修改了 PR 描述,还插入了它自身以及 Raycast 的广告。这一行为引发了用户的强烈反应,有…...
CMake实战:用ExternalProject_Add一键集成第三方库(附spdlog完整配置)
CMake实战:用ExternalProject_Add一键集成第三方库(附spdlog完整配置) 在C项目开发中,第三方库的集成往往是最耗时的环节之一。传统的手动下载、编译、配置头文件路径和链接库文件的方式,不仅效率低下,还会…...
从七鳃鳗到潜水器:手把手教你用Python生态学模型搞定2024美赛A、B题
从七鳃鳗到潜水器:Python生态学建模实战指南 数学建模竞赛中,生态学问题往往让参赛者望而生畏——复杂的生物系统、多变的环境参数、非线性相互作用,这些要素叠加起来容易让人陷入理论推导的泥潭。但换个角度看,这正是Python科学计…...



