【JavaEE】多线程案例-线程池

文章目录
- 1. 什么是线程池
- 2. 为什么要使用线程池(线程池有什么优点)
- 3. 如何使用Java标准库提供的线程池
- 3.1 创建一个线程池对象
- 3.2 什么是工厂模式
- 3.3 为什么要使用工厂模式
- 3.4 Executors 创建不同具有不同特性的线程池
- 3.5 ThreadPool 类的构造方法
- 3.6 线程池的拒绝策略
- 3.7 调用 submit 方法添加任务
- 4. 自己实现一个线程池
1. 什么是线程池
线程池是一种多线程处理形式,它处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池中的线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。线程池避免了在处理短时间任务时创建与销毁线程的代价,从而提高了程序的效率和性能。
2. 为什么要使用线程池(线程池有什么优点)
我们都知道,在 Java 中使用多进程效率是比较低的,因为进程的创建和销毁的开销是比较大的,这样就会导致进程的创建和销毁的速度比较慢。所以在多进程的基础上就出现了线程。线程的创建和销毁都比较轻量,多个线程共用一套资源,这就避免了多次向计算机申请资源,极大提高了代码的执行速度。但是如果一个线程多次创建和销毁的话,也会导致系统资源的频繁调用,并且创建和销毁线程的而操作是内核态的,计算机通过调用相关的 API,然后进行线程的创建和销毁,但是既然是内核态操作,那么在计算机创建和销毁线程的过程中可能不是只干了这一件事,可能还会顺便帮其他线程提供资源等,这样就降低了代码的执行速度,所以为了解决线程多次创建和销毁,并且保证线程的创建和销毁属于用户态的操作的问题,就出现了线程池这一概念。在线程池中会提前创建 n 个线程,这些线程在执行完后不会销毁,而是继续存储在线程池当中等待下一次调用,正是因为线程池的这一概念,就使得线程创建和销毁的频率降低了。
总结来说,线程池的优点有以下这些:
- 降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务无需等待线程创建,可以立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配,调优和监控。
- 避免系统过度切换:如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者产生“过度切换”的问题。
3. 如何使用Java标准库提供的线程池
3.1 创建一个线程池对象
在Java的 java.util.concurrent 包中提供了线程池相关的方法。那么如何创建出能执行线程池相关操作的对象呢?
ExecutorService service = Executors.newScheduledThreadPool(3);

ExecutorService 是Java中的一个接口,它继承了Executor接口。
ExecutorService 接口在 java.util.concurrent 包中,它用于管理线程池。它提供了一种方式来管理和控制线程的生命周期。具体来说,它用于创建和管理线程池,可以执行线程,也可以关闭线程池。
而 Executors 则是一个工厂类,用来创建不同类型的 ThreadPoolExecutor 实例。
看到工厂类就需要提到一个常用的模式——工厂模式了,那么什么又是工厂模式呢?
3.2 什么是工厂模式
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式,通过将对象的实例化过程封装在工厂类中,使得创建对象的方式更加灵活和可扩展。
在工厂模式中,客户端代码只需关注接口,而无需关注对象的具体创建过程。工厂模式通过提供一个统一的接口来创建不同类型的对象,这个接口定义了创建对象的标准方式。

3.3 为什么要使用工厂模式
工厂模式的作用是用来创建一个类的不同类型对象,既然这样的话,我们在一个类中使用多种重载的构造方法不就好了吗,为什么要多此一举再创建一个工厂类来创建一个类的不同类型的对象呢?
如果我们不使用工厂类来创建不同类型的对象,那么在创建对象的时候就需要在客户端中显式地选择合适的构造方法并提供对应的参数,这样的话类的具体创建逻辑就暴露了。而使用工厂模式的话,客户端代码只需调用工厂类的接口即可,而无需了解具体的创建逻辑。这样可以将对象的创建与使用代码分离,使得系统更加灵活,可扩展性更强。同时,使用工厂模式还可以避免在客户端代码中暴露对象的创建逻辑,提高了系统的安全性。

当创建线程池对象的时候,我们只需要调用 Executors 工厂类的对应静态方法,并且传递对应的参数就可以得到不同类型的 ThreadPoolExexutor 实例了,通过这个工厂模式既实现了创建一个类的不同实例的功能,又保证了系统的安全性。
3.4 Executors 创建不同具有不同特性的线程池
在知道什么是工厂模式之后,我们就利用这个工厂类来创建出需要的线程池实例,那么 Executors 工厂类又提供了哪些创建线程池对象的方法呢?它们又分别具有什么特性呢?
newFixedThreadPool() 方法


newCachedThreadPool() 方法


newScheduledThreadPool() 方法


Executors 工厂类还有很多不同的创建线程池对象的方法,这里我就不给大家一一展示出来了,大家如果感兴趣的话,可以去Java帮助文章上去查看。
3.5 ThreadPool 类的构造方法
通过查看源码,我们可以知道,Executors 工厂类创建的线程池对象都是通过传递不同的参数来实例化 ThreadPool 类的,也就是说 ThreadPool 类具有多种构成重载的构造方法,那么来看看这些不同的构造函数的参数分别代表什么吧。

- corePoolSize 表示线程池中的核心线程数
- maximumPoolSize 表示线程池中可含有的最大线程数
- keepAliveTime 表示当线程池中的线程数量超过核心线程数(corePoolSize)时,多余的空闲线程在终止之前等待新任务的最长时间。
- TimeUnit unit 用于指定keepAliveTime参数的时间单位。
- workQueue 表示阻塞队列,可以根据需要设置阻塞队列的类型,如果需要优先级,则可以使用PriorityBlockingQueue;如果不需要优先级且任务的数目是恒定的,则可以使用ArrayBlockingQueue;如果任务的数目不是恒定的,则可以使用LinkedBlockingQueue
- ThreadFactory 表示工厂类
- RejectedExecutionHandle handle 表示拒绝策略
这里解决策略是面试中容易考的高频考点,那么这里我们就来详细的说说关于线程池的拒绝策略。
3.6 线程池的拒绝策略
当线程池中容纳的任务数量到达了最大限制之后,如果继续往里面添加任务的话,会出现什么情况呢?Java 中提供了4种拒绝策略。
- ThreadPoolExecutor.AbortPolicy 抛出异常
- ThreadPoolExecutor.CallerRunsPolicy 新添加的任务由添加任务的线程执行该任务
- ThreadPoolExecutor.DiscardOldestPolicy 丢弃掉最旧的未被处理的请求
- ThreadPoolExecutor.DiscardPolicy 丢弃掉当前新加的任务




3.7 调用 submit 方法添加任务
当创建了适当的线程池对象并且了解了其中创建的细节了之后,我们就需要调用该线程对象的相关方法来执行代码。
使用 submit 方法来添任务。
public class Demo1 {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("线程1");}});service.submit(new Runnable() {@Overridepublic void run() {System.out.println("线程2");}});service.submit(new Runnable() {@Overridepublic void run() {System.out.println("线程3");}});service.submit(new Runnable() {@Overridepublic void run() {System.out.println("线程3");}});}
}

4. 自己实现一个线程池
同样的虽然 Java 标准库提供了线程池,但是我们作为初学者如果能够自己实现一个线程池,那么对于我们理解其中的逻辑和细节很有帮助。
class MyThreadPool {//创建一个阻塞队列BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();//实现submit方法public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}//实现构造方法,类创建的时候就会执行任务public MyThreadPool(int n) {for(int i = 0; i < n; i++) {Thread t = new Thread(() -> {Runnable runnable = null;try {runnable = queue.take();} catch (InterruptedException e) {throw new RuntimeException(e);}runnable.run();});t.start();}}
}
测试
public class Demo2 {public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for(int i = 0; i < 4; i++) {int id = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行线程 " + id);}});}}
}

由于使用的是阻塞队列,所以当线程池中的任务达到数量限制的时候,如果再添加任务,会进入阻塞等待状态,这是不同于Java标准库提供的四种拒绝策略。
相关文章:
【JavaEE】多线程案例-线程池
文章目录 1. 什么是线程池2. 为什么要使用线程池(线程池有什么优点)3. 如何使用Java标准库提供的线程池3.1 创建一个线程池对象3.2 什么是工厂模式3.3 为什么要使用工厂模式3.4 Executors 创建不同具有不同特性的线程池3.5 ThreadPool 类的构造方法3.6 线…...
服务器搭建(TCP套接字)-fork版(服务端)
基础版的服务端虽然基本实现了服务器的基本功能,但是如果客户端的并发量比较大的话,服务端的压力和性能就会大打折扣,为了提升服务端的并发性能,可以通过fork子进程的方式,为每一个连接成功的客户端fork一个子进程,这样…...
缺失的第一个正数:高效解法与技术
缺失的第一个正数:高效解法与技术 背景 在计算机编程中,有时候需要寻找一个未排序整数数组中没有出现的最小的正整数。这篇技术博客将详细讨论这个问题,并提供一个时间复杂度为 O(n) 且只使用常数级别额外空间的解决方案。 问题描述 leet…...
常用的辅助网站(持续更新)
标题 一、uni-app方向二、H5方向 一、uni-app方向 1、uni-app官网 地址:https://uniapp.dcloud.net.cn/ 2、香蕉云编 地址:https://www.yunedit.com/ 描述:一般用来配置ios证书或安卓证书、上传ios包至商店 3、uView 地址:http…...
LeetCode 75 - 01 : 最小面积矩形
type pair struct{x, y int }func minAreaRect(points [][]int)int{mp : map[pair]struct{}{}// 将二维数组中的坐标映射到map中for i : range points{mp[pair{points[i][0], points[i][1]}] struct{}{}}// 将结果设置为一个最大值,防止影响求最小值的逻辑res : ma…...
每日一题:请解释什么是闭包(Closure)?并举一个实际的例子来说明。(前端初级)
今天继续在前端初级笔试题中被AI虐: 碱面的答案,问题:初级,回答:初级https://bs.rongapi.cn/1702510598371151872/14我的回答如下: 闭包是指由大括号包裹的一个区域,这个区域代表了一个变量生效…...
广告主必看!NetMarvel五大优势驱动出海App投放增长
App出海走到今天,流量红利早就不存在,摆在广告主面前最棘手的两个问题,一是不起量,二是买量成本太高,得不偿失。 如何在确保出海应用用户规模有所增长的同时,也保证整体ROI处在较高水平?NetMar…...
数据结构与算法之复杂度
时间复杂度 1.抓大头 2.常数用o(1),低阶函数也用o(1)代替(直接去掉) 3.取最坏情况 对数相关写法的规定...
ATECLOUD电源测试软件平台如何测试电源纹波?
电源纹波是影响电源稳定性的重要因素,过大的纹波会导致电源模块的工作效率降低,可能使电源模块直接损坏。使用ATECLOUD碘盐测试软件平台对纹波进行测试,检测其工作情况,以确保其稳定性和性能。 电源纹波的产生 电源的纹波通俗的来…...
数据结构与算法:排序算法(2)
目录 堆排序 使用步骤 代码实现 计数排序 适用范围 过程 代码实现 排序优化 桶排序 工作原理 代码实现 堆排序 二叉堆的特性: 1. 最大堆的堆顶是整个堆中的最大元素 2. 最小堆的堆顶是整个堆中的最小元素 以最大堆为例,如果删除一个最大堆的…...
1_图神经网络GNN基础知识学习
文章目录 安装PyTorch Geometric安装工具包 在KarateClub数据集上使用图卷积网络 (GCN) 进行节点分类两个画图函数Graph Neural Networks数据集:Zacharys karate club network.PyTorch Geometric数据集介绍 edge_index使用networkx可视化展示 Graph Neural Networks…...
瑞芯微:基于RK3568的ocr识别
光学字符识别(Optical Character Recognition, OCR)是指对文本资料的图像文件进行分析识别处理,获取文字及版面信息的过程。亦即将图像中的文字进行识别,并以文本的形式返回。OCR的应用场景 卡片证件识别类:大陆、港澳…...
C++真的是 C加加
📝个人主页:夏目浅石. 📌博客专栏:C的故事 🏠学习社区:夏目友人帐. 文章目录 前言Ⅰ. 函数重载0x00 重载规则0x01 函数重载的原理名字修饰 Ⅱ. 引用0x00 引用的概念0x01 引用和指针区分0x03 引用的本质0x04…...
java学习--day5 (java中的方法、break/continue关键字)
文章目录 day4作业今天的内容1.方法【重点】1.1为什么要有方法1.2其实已经见过方法1.3定义方法的语法格式1.3.1无参无返回值的方法1.3.2有参无返回值的方法1.3.3无参有返回值的方法1.3.4有参有返回值的方法 2.break和continue关键字2.1break;2.2continue; 3.案例关于方法的练习…...
MFC主框架和视类PreCreateWindow()函数学习
在VC生成的单文档应用程序中,主框架类和视类均具有PreCreateWindow函数; 从名字可知,可在此函数中添加一些代码,来控制窗口显示后的效果; 并且它有注释说明, Modify the Window class or styles here by…...
for forin forof forEach map区别
一、总结 相同点:都是串行遍历。不同点: 二、for of循环 设计目的:遍历所有数据结构的统一方法。原理:会调用数据结构的Symbol.iterator方法。 只要数据结构定义了Symbol.iterator属性,就能用for of遍历它的成员。…...
特殊时间(蓝桥杯)
特殊时间 问题描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 2022年2月22日22:20 是一个很有意义的时间, 年份为 2022 , 由 3 个 2 和 1 个 0 组成, 如果将月和日写成 4 位, 为 0222 , 也是由 3 个 2 和 1 个 0 组 成…...
VUE路由与nodeJS环境搭建
VUE路由 Vue路由是Vue.js提供的路由管理工具,它允许我们在应用程序中实现页面之间的导航,从而使单页面应用程序的开发更加方便。通过Vue路由,我们可以轻松地创建和管理多个视图,并在这些视图之间导航。 Vue路由使用HTML5的Histo…...
抗锯齿的线
抗锯齿的线 右下角的时候h是0,到顶部 h是1,然后中间y相距4个像素,那dy就是0.25 如果让h abs(fract(h - 0.5) - 0.5) 中间一行0.5,第一行 第三行都是0.25,两端都是0 根据插值来看 这里是 如果用h/dy 那么第一行以上࿰…...
如何使用高压放大器驱动高容性负载
使用高压放大器驱动高容性负载是一个具有挑战性的任务,需要仔细考虑电路设计和操作技巧。下面西安安泰Aigtek将为您介绍一些关于如何使用高压放大器驱动高容性负载的方法和注意事项。 首先,让我们了解一下高容性负载。高容性负载通常指电容值较大的负载元…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
