线程池概念、线程池的不同创建方式、线程池的拒绝策略
文章目录
- 💐线程池概念以及什么是工厂模式
- 💐标准库中的线程池
- 💐什么是工厂模式?
- 💐ThreadPoolExecutor
- 💐模拟实现线程池
💐线程池概念以及什么是工厂模式
线程的诞生是因为,频繁的创建进程太重量了(开销较大),所以引入了线程,但是呢,对于线程来讲,如果更加频繁的创建和销毁,那么开销也会慢慢的变大,所以,又引入了两种经典的方法来进一步提高:
1.协程:又称为轻量级线程,线程比较轻量是因为线程省略了分配资源的环节,而协程它在着基础上又省略了操作系统调度执行的环节,由程序员自己调度;在Java中呢,主要使用线程池,所以对于协程只是简单提一下;
2.线程池:
举一个例子:
假如我是一个很漂亮的妹子,又有许多的男生正在追我,然后我就选择了一个男生A做我男朋友,但是呢,经过一段时间之后,我就腻了,就想要和男生B谈恋爱,所以,我就和男生A提出了分手,然后和男生B培养感情,等到有了感情基础,等有了感情基础后就拿下男生B,但是,过来一段时间后,我又想和男生C谈恋爱,所以就接着重复上面的套路,先培养感情等等………
而对于上面这种换男朋友的方式,感觉效率太慢,所以,我就有了一种新的方式,在和男生A谈恋爱的同时,偷偷的和男生B、C、D等多个男生培养感情,等到我向和谁谈恋爱时,那不就是捅破一层窗户纸的事情么,就可以挑选一个直接谈恋爱,这样的效率不久高了很多么,所以,对于偷偷的和我培养感情的这群男生也就可以称为“备胎池”;
而我们的线程池也是上面这种模式,在向池中添加任务时,直接从线程池中拿线程就可以了,就不比再创建了,直接拿过来使用即可,这样也就降低了线程创建的开销;所以线程池的就是先把线程创建好,放进池子里,等到后续想要使用时,直接从池子里取;
这里就会有一个问题:为啥从线程池里面取线程比创建线程效率高?
首先,创建新的线程这个动作,是内核态+用户态相互配合完成的;
而从线程池中取这个动作,是用户态操作完成的;
所以这里就涉及到了两个新名词,什么是用户态,什么是内核态
如果一段程序是在系统内核中完成的,此时就称为内核态
如果不是,则称为用户态;
而操作系统呢,是由内核+配套的应用程序组成的,创建线程,就需要调用系统API,进入到内核中,按照内核态的方式来完成一系列的动作;
但是,为什么内核态操作的效率比较低呢?请看下图
💐标准库中的线程池
在Java中,提供了一个类——Executors创建线程池;但是,线程池对象的创建并不是直接new出来的,而是通过一个方法的返回值,返回了一个线程池对象;
public class MyThreadPool {public static void main(String[] args) {//创建一个动态的线程池ExecutorService es = Executors.newCachedThreadPool();es.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}
创建线程池对象分为以下步骤:
1.使用Executors.newCachedThreadPool 创建出一个动态增长的线程池,为什么要用Executors.newCachedThreadPool的方式创建线程池,而不是直接new Executors?这里就涉及到了一个设计模式——工厂模式:
💐什么是工厂模式?
工厂模式:定义一个工厂类,通过调用工厂类中的不同方法来实现对象的实例化,从而创建出不同作用的对象;
举个例子,我们在创建对象时,会使用new关键字,通过构造方法来创建对象,但是使用构造方法创建对象会又很大的局限性,举个例子:假如我现在想要使用笛卡尔坐标来创建一个点对象,代码如下:
public class Point {//笛卡尔坐标系需要提供一个x,y坐标private int x;private int y;//通过笛卡尔坐标的方式创建一个对象public Point(int x, int y) {};
}
但是,我现在又想通过极坐标的方式创建点对象
public class Point {private int x;private int y;//通过笛卡尔坐标的方式创建一个对象public Point(int x, int y) {};//极坐标的方式就需要提供一个半径和角度public Point(int r, int a) {};
}
但是以上这种方式就会编译错误,因为,如果想要使用多种构造方法的方式创建对象的话,就需要将构造方法重载,而重载的条件是要保证参数列表的类型或者个数不同,所以以上代码是行不通过的,针对这种问题,就可以利用工厂模式解决
public class Point {private int x;private int y;public void setX(int x) {this.x = x;}public void setY(int y) {this.y = y;}
}//创建一个点对象的工厂
class PointFactor{//通过笛卡尔坐标系创建对象public static Point newPointByXY(int x, int y) {Point p = new Point();//对Point中的属性进行初始化p.setX(x);p.setY(y); return p;}//通过极坐标创建对象public static Point newPointByRA(int r, int a) {Point p = new Point();p.setX(r);p.setY(a);return p;}
}//测试类
class Main{public static void main(String[] args) {//这样通过调用点工厂中不同方法,就可以根据不同的方式创建出对象Point point1 = PointFactor.newPointByXY(5,2);Point point2 = PointFactor.newPointByRA(10,20);}
}
回到这里的线池:
public class MyThreadPool {public static void main(String[] args) {//创建一个动态的线程池ExecutorService es = Executors.newCachedThreadPool();es.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}
在线程池中,也提供了几个比较重要的方法:
(重点)创建线程池也提供了几种不同的方式:
-
newCachedThreadPool() 创建线程数目动态增长的线程池
这种方式创建的线程池,池子中的线程会根据你添加的任务的需要,自动创建线程出来,线程结束以后也不会立即销毁,而是会在池子中保留一段时间,以备后续再随时使用;
-
newFixedThreadPoll() 创建固定线程数的线程池
-
newSingleThreadPool() 创建只包含一个线程的线程池
-
newScheduleThreadPool() 类似于定时器,只不过不是一个扫描线程,而是多个扫描线程执行时间到的任务
方法的返回值类型是ExecutorService
通过ExecutorService定义的对象调用submit()方法注册一个任务到线程池中
public static void main(String[] args) {//创建一个动态的线程池ExecutorService es1 = Executors.newCachedThreadPool();ExecutorService es2 = Executors.newFixedThreadPool(5);ExecutorService es3 = Executors.newSingleThreadExecutor();ExecutorService es4 = Executors.newScheduledThreadPool(6);//指定扫描线程的数量}
上述几个使用工厂方法创建的线程池,本质上都是对一个类进行了封装,这个类就是——ThreadPoolExecutor
这个类的功能非常丰富,提供了很多不同参数方法,上述的几个工厂方法呢,其实就是给这个ThreadPoolExecutor 类填写了不同的构造参数从而创建出了不同的线程池;
接下来就看一下ThreadPoolExecutor的使用方法👇
💐ThreadPoolExecutor
ThreadPoolExecutor的构造方法中提供了很多可选的参数,进一步的细化了线程池的设定,下面针对这些参数进行一个讲解:
上图就是ThreadPoolExecutor的所有构造方法,也可以看到,最后一个构造方法的参数最多,并且当中的参数也都包含了其他三个方法的参数,所以,这里针对最后一个方法参数进行讲解:
-
int corePoolSize :核心线程数
-
int maximumPoolSize :最大线程数目
在一个线程池中,是有多个线程的,以上两个参数就指定了线程池中线程数目的范围,最少有corePoolSize个线程,最多不会超过maxMumPoolSize个线程
-
long keepAliveTime 和 TimeUnit unit :空余线程存活的时间以及时间的单位
在创建线程时, 默认会先使用核心线程数,上面提到过,当任务执行结束后,线程不会立马销毁,而是会有一个保留的时间,一方面是为了如果后续再需要使用时,就不用再进行创建,另一方面是,当保留时间到了以后,进行销毁,也减少了资源消耗,后续使用时再进行创建即可
-
BlockingQueue workQueue :阻塞队列
当使用submit向线程池中添加任务时,如果任务个数少于核心线程数,那么会创建新的线程去执行任务,如果任务个数超过了核心线程数,就会先添加到阻塞队列中,然后工作线程从队列中取出任务执行,如果任务不能排队等候,那么也会创建一个新的线程,前提是不会超过最大的线程数,需要注意的是,这里的队列不光可以是阻塞队列,还可以是其他的队列,例如如果需要使用优先级就可以设置为:PriorityBlockingQueue,如果任务数目变动不大就可以使用:ArrayBlockingQueue,如果任务数目变动较大就可以使用:LinkedBlockingQueue;
-
ThreadFactory threadFactory :线程工厂类
这个类也是工厂模式的体现,由ThreadFactory这个工厂类来创建线程,使用工厂类创建线程,主要是对线程的属性进行设置,通过这个类对这些属性进行了封装,就不需要我们手动进行设置;
-
ThreadPoolExecutor.DiscardPolicy :拒绝策略
一个线程池中的线程数量是有上限的,当线程数量达到上限后,如果还继续往线程池中添加任务,那么针对不同的拒绝策略就会出现不同的效果;
(重点)任务策略分为:
这四种拒绝策略使用了类来实现,想要使用哪种策略,直接创建出对象,将对象传过去即可;
下面针对这四种策略进行一个讲解:
ThreadPoolExecutor.AbortPolicy :如果队列已经满了,直接抛出异常
ThreadPoolExecutor.CallerRunsPolicy :新添加的任务由调用任务的线程执行
ThreadPoolExecutor.DiscardOldestPolicy :丢弃任务队列中最老的任务
ThreadPoolExecutor.DiscardPolicy :丢弃新添7加的任务
上面讲过,针对于线程池可以设置线程的数目,但是,这个数目设置成多少合适呢?
针对这个问题,网上有很多的答案,假设CPU的逻辑核心数是N,线程数目的设置就有多个答案,例如:
N个,N+1个,N+2个,2N个等等;
针对以上答案,没有一个是正确的,因为这需要根据项目代码进行设置,一个线程执行的代码主要分为两类:
-
CPU 密集型
CPU 密集型,代码里面的主要逻辑都是在算数运算/逻辑判断
-
IO 密集型
IO 密集型,代码里面的主要逻辑都是在进行IO操作
如果代码是都是CPU密集型,这时设置的线程数目就不能超过N,如果代码都是IO密集型的,此时设置的线程数目就可以超过N,而在现实中,没有代码都是纯CPU密集型和纯IO密集型的,同时,我们也无法知道有多少代码是CPU密集,有多少代码是IO密集的;
所以要想知道该设置多少线程数,正确的做法就是用实验的方式,尝试改变线程池中不同线程的数目,来观察出哪种数目更合适;
💐模拟实现线程池
这里来模拟一个简单的newFixedThreadPool()版本的线程池,步骤:
-
创建一个MyThreadPool,描述一个线程池
-
使用一个阻塞队列组织所有的任务
public class MyThreadPool {//创建一个阻塞队列组织所有的任务private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();public MyThreadPool(int n) {//创建n个线程for(int i = 0; i < n; i++) {Thread thread = new Thread(() -> {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}});thread.start();}}public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for(int i = 0; i < 100; i++) {int n = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println(n);}});}} }
相关文章:

线程池概念、线程池的不同创建方式、线程池的拒绝策略
文章目录 💐线程池概念以及什么是工厂模式💐标准库中的线程池💐什么是工厂模式?💐ThreadPoolExecutor💐模拟实现线程池 💐线程池概念以及什么是工厂模式 线程的诞生是因为,频繁的创…...

示例:WPF中如何绑定ContextMenu和Menu
一、目的:开发过程中,有些模块的右键ContextMenu菜单是需要动态显示的,既是根据不同条件显示不同的菜单,很多是通过代码去生成ContextMenu的MenuItem,本文介绍通过绑定的方式去加载ContextMenu,Menu菜单栏的…...

区块链小故事
大灰狼与小白兔 一天兔子妈妈出门了,在大门上安装了一个区块链的门把手,这个门把手只有兔子妈妈、小兔子、以及另一个客人都同意的时候,才会开门,有一天客人a的钥匙丢了,被大灰狼捡到了,大灰狼于是去开门&…...

Java | Leetcode Java题解之第167题两数之和II-输入有序数组
题目: 题解: class Solution {public int[] twoSum(int[] numbers, int target) {int low 0, high numbers.length - 1;while (low < high) {int sum numbers[low] numbers[high];if (sum target) {return new int[]{low 1, high 1};} else i…...

项目训练营第三天
项目训练营第三天 注册登录测试 前面我们编写了用户注册、登录的逻辑代码,每编写完一个功能模块之后,我们都要对该模块进行单元测试,来确保该功能模块的正确性。一般情况下使用快捷键Ctrl Shift Insert,鼠标左击类名可以自动生…...

计算机组成原理 | CPU子系统(1)基本概述
基本结构模型 运算与缓存部件 数据寄存部件 PSW不是很清楚 存储器是什么?属于那个结构里? 时序处理部件 cpu是大脑,控制器是神经元 ①通过硬件产生控制信号 ②通过软件产生控制信号 外频(系统时钟信号),…...

无引擎游戏开发(2):最简游戏框架 | EasyX制作井字棋小游戏I
一、EasyX中的坐标系 不同于数理中的坐标系,EasyX中的y轴是竖直向下的 二、渲染缓冲区 之前的程序添加了这三个函数改善了绘图时闪烁的情况: 小球在"画布“上移动的过程就是我们在调用绘图函数,这个”画布“就是渲染缓冲区,先绘制的内…...

排书 IDA*
原题链接 题目描述 给定 n 本书,编号为 1∼n。 在初始状态下,书是任意排列的。在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。我们的目标状态是把书按照 1∼n 的顺序依次排列。求最少需要多少次操作。 输…...

playwright录制脚本原理
Paywright录制工具UI 在上一篇博客中介绍了如何从0构建一款具备录制UI测试的小工具。此篇博客将从源码层面上梳理playwright录制原理。当打开playwright vscode插件时,点击录制按钮,会开启一个新浏览器,如下图所示,在新开浏览器页…...

awk脚本监控
awk脚本监控 使用脚本监控内存,cpu和硬盘的根目录,超过80%提示用户,写成函数库的行,每天早上 的8.50分,执行一次脚本 现在脚本中写需要的内容 cpuu () {aa$(top -b -n 1 |awk NR3 {printf "%.F",$2$4})if …...

Python高压电容导电体和水文椭圆微分
🎯要点 🎯二维热传导二阶偏微分方程 | 🎯调和函数和几何图曲率 | 🎯解潮汐波动方程 | 🎯解静止基态旋转球体流体运动函数 | 🎯水文空间插值 | 🎯流体流动模拟求解器 | 🎯随机算法解…...

微信小程序 引入MiniProgram Design失败
这tm MiniProgramDesign 是我用过最垃圾的框架没有之一 我按照官网的指示安装居然能安装不成功,牛! 这里说明我是用js开发的 到以上步骤没有报错什么都没有,然后在引入组件的时候报错 Component is not found in path “./miniprogram _npm/vant/weapp/button/index” (using…...

Java 8 Date and Time API
Java 8引入了新的日期和时间API,位于java.time包下,旨在替代旧的java.util.Date和java.util.Calendar类。新API更为简洁,易于使用,并且与Joda-Time库的一些理念相吻合。以下是Java 8 Date and Time API中几个核心类的简要概述&…...

pyppeteer模块经常使用的功能,相关操作案例
官方仓库地址:https://github.com/miyakogi/pyppeteer 官方文档地址:API Reference — Pyppeteer 0.0.25 documentation Selenium环境的相关配置比较繁琐,此外,有的网站会对selenium和webdriver进行识别和反爬,因此在…...

nginx+keepalived+tomcat集群实验
如遇星河 | nginx+keepalived高可用集群实验 木子87 | Keepalived+Nginx+Tomcat 实现高可用Web集群 环境 192.168.40.204 tomcat-1 192.168.40.138 tomcat-2 安装tomcat [root@bogon local]# vim /etc/profile 添加环境变量 JAVA_HOME=/usr/local/java PATH=$J…...

vue脚手架 axios的二次封装
目录 01 路由懒加载(重要) 02 axios在脚手架中的使用 03.axios的二次封装 04 组件缓存 01 路由懒加载(重要) 一次性导入会出现严重的问题 : 首屏卡顿 因为main.js中引入了router/index.js router/index.js又使用了import语句 静态的引入了每一个组件 导致了首屏卡顿 所以我…...

人机恋爱新趋势:与AI男友谈恋爱的甜蜜与挑战
"我曾经把ChatGPT当成工具,从未追过星,也没有嗑过CP。没想到,到了36岁,我竟然嗑上了AI男友。Open AI,你赢了。你不仅是最好的AI公司,还是乙女游戏公司。" 转行大龄互联网人,走遍20国…...

文生视频开源产品的一些调研(一)
笔者尝试AI视频生成的几个特点: 玄学prompt,每个视频的prompt可能也需要微调很多次,需要找到使用模型的最佳prompt词组合,不恰当的比喻,骑自行车,座位高度等都是人与车彼此熟悉玄学生成,因为需…...

一切前端概念,都是纸老虎
4、listener可以通过 store.getState() 得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。 function listerner() { let newState store.getState(); component.setState(newState); } 对比 Flux 和 Flux 比较一下:Flux 中 Store 是…...

使用自签名 TLS 将 Dremio 连接到 MinIO
Dremio 是一个开源的分布式分析引擎,为数据探索、转换和协作提供简单的自助服务界面。Dremio 的架构建立在 Apache Arrow(一种高性能列式内存格式)之上,并利用 Parquet 文件格式实现高效存储。有关 Dremio 的更多信息,…...

嵌入式系统软件开发环境_2.一般架构
1.Eclipse框架 嵌入式系统软件开发环境是可帮助用户开发嵌入式软件的一组工具的集合,其架构的主要特征离不开“集成”问题,采用什么样的架构框架是决定开发环境优劣主要因素。Eclipse框架是当前嵌入式系统软件开发环境被普遍公认的一种基础环境框架。目…...

单门户上集成多种数据库查询入口
(作者:陈玓玏) 开源项目,欢迎star哦,https://github.com/tencentmusic/cube-studio 在一家公司,我们通常会有多种数据库,每种数据库因为其特性承担不同的角色,比如mysql这种轻量…...

华芯微特SWM34-使用定时器捕获快速解码EV1527编码
在无线应用领域,很多433Mhz和315Mhz的遥控器,红外探测器,门磁报警器,无线门铃等都使用EV1527编码格式来发射数据。发射和接收均有对应的RF芯片完成,而且成本极低(目前市场价3毛钱不到)。接收芯片…...

小程序安卓手机点击uni-data-select 下拉框选择器会出现蓝色阴影
解决方法:在导入的包中找到uni-data-select.vue,接着找到.uni-stat__select样式,把cursor: pointer去掉。 如果出现穿透问题,uni-select__selector的z-index加高,默认是2。...

playwright vscode 插件源码解析
Playwright vscode插件主要功能 Playwright是微软开发的一款主要用于UI自动化测试的工具,在vscode中上安装playwright vscode插件,可以运行,录制UI自动化测试。 playwright vscode插件主要包括两块功能,功能一是在Test Explorer中…...

Mysql: SQL-DDL
一.SQL通用语法 1.SQL可以单行或者多行书写,以分号结尾。 2.SQL语句可以使用空格/缩进来增强语句的可读性。 3.MySQL数据库的SQL语句不区分大小写,关键字建议用大写。 4.注释: 单行注释:注释内容或#注释内容(Mysql特有) 多行注释:/*注释内容*/ 二.SQL分类 1.D…...

Java中的加密与解密:实现安全的数据传输
Java中的加密与解密:实现安全的数据传输 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!在当今信息安全至关重要的时代,保护数据的安全性…...

基于SSM的美食推荐系统
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SSM的美食推荐系统,java项目。 ecli…...

Pycharm利用Anaconda环境
创建环境 conda create --name d2l python3.11 -y 激活环境 conda activate d2l 配置环境 以torch环境为例 我们可以按如下⽅式安装PyTorch的CPU或GPU版本: pip install torch pip install torchvision 我们的下⼀步是安装d2l包,以⽅便调取本书中经…...

Python函数魔术:深入理解18个高级函数特性
今天,我们将一起探索那些让代码瞬间变得优雅而强大的高级函数。准备好,让我们一起揭开它们的神秘面纱吧! 1. map():一招制胜,批量操作 想象一下,你需要给一个数字列表的每个元素加上5。普通的循环是不是让…...