当前位置: 首页 > news >正文

【Java EE初阶九】多线程案例(线程池)

一、线程池的引入

        引入池---->主要是为了提高效率;

        最开始,进程可以解决并发编程的问题,但是代价有点大了,于是引入了 “轻量级进程” ---->线程

        线程也能解决并发编程的问题,而且线程的开销比进程要小的多,但是如果线程太多,创建销毁线程的频率也会进一步提高,故此线程创建销毁的开销就不能忽视了。

        为了解决上述问题,大佬们给出了两个解决方案:

      1、引入轻量级线程---->也称为纤程/协程(节省了系统调度的开销)

        协程的本质是程序员在用户态代码中进行调度,不是靠内核的调度器调度的—>节省了许多的调度上的开销协程是在用户代码中,基于线程封装出来的,可能是N个协程对应1个线程,也可能是N个协程对应M个线程。

        2、引入 “线程池”

      线程池:把我们要使用的线程池提前创建好,这个线程执行完也不要直接释放而是存放到线程池中以备下次继续使用需要用这个线程的时候,再从线程池中拿,不需要的时候,就放在线程池中,并不会销毁它,这样就节省了创建/销毁线程的开销;

        在这个使用的过程中,并没有真正的频繁创建销毁,而只是从线程池里面取线程使用,等使用完了在还给线程池;

        为啥从线程池中取线程 比从系统中申请线程的创建更高效呢?

        下面讲解一下关于用户态和内核态的说明;

        假设在银行场景中,smallye要去这个银行办理一个业务,一般银行中大堂有复印机;这时,smallye没有带身份证复印件,此时smallye要去搞到身份证复印件,有两个选择:

        其一选择:把身份证给柜员,让柜员帮smallye复印,但是这个操作是不可控的,可能这个柜员中途被老板安排了其他活,那这个时候,就不能帮smallye复印身份证了,要等忙完老板安排的活,再帮smallye复印身份证;

        其二选择:smallye自己去大堂中复印身份证,这样就比较可控了,smallye可以很快的去到打印机,立马复印出来,再去办理他的业务。如下图所示:

        上述例子中大堂就是用户态,柜台就是内核态;

        从线程池中取线程,是纯用户态代码(可控)                                                                           通过系统申请创建线程,需要内核完成(不可控有风险);

2. 线程池的简单介绍

2.1 ThreadPoolExecutor类

        在java标准库中,ThreadPoolExecutor类表示线程池,ThreadPoolExecutor类是参数最多的构造方法,如下图所示:

        下面来详细讲解该构造方法里面的参数的具体含义:

         1、核心线程数和最大线程数(int corePoolSize,int maximumPoolSize):

        corePoolSize:核心线程数:(正式员工线程)

        maximumPoolSize:最大线程数:(正式员工线程 + 实习员工线程)

        eg:核心线程就是相当于公司里面的正式员工,同时最大线程数里面包含最大线程数和实习员工线程,对于实习员工线程来说就是就是可有可无的,当核心线程全部处于工作状态且还有大量的任务需要新的线程处理的时候,我们就会创建实习员工线程,来帮核心线程处理这些任务;当任务数量较少的时候,核心线程可以闲着,但是实习员工线程全部需要销毁;

        2、保持存活时间和存活时间的单位(long KeepAliveTime,TimeUnit unit)

        KeepAliveTime:保持存活时间:(实习生线程允许摸鱼的最大时间)

        unit:存活时间的单位:可以是hour 、 min 、 s 、 ms

        3、放任务的队列   (BlockkingQueue<Runnable> workQueue:)

        和定时器类似,线程池中也可以持有多个任务,要执行的任务,使用Runnable来描述任务的主体。

        4、线程工厂(ThreadFactory,threadFactory)

        通过这个工厂类创建线程对象(Thread对象),工厂类里面有方法封装了new Thread的操作,同时给Thread设置了一些属性,我们想要创建线程的时候可以直接使用工厂类的方法创建。

        eg:描述一个点,通过数学知识可以用二维坐标和极坐标来表示:二维坐标:(x,y) 极坐标:(r,α);故此我们通过new一个类来得到一个点,这个类里有两个构造方法,参数分别是(double x,double y),(double r,double α),那么这两个构造方法的参数类型都一样,构成不了重载,如下图所示:

        以上显示出我们想要给java类提供更多的构造方法,但是受到重载的影响限制,为了解决上述问题,我们引入了“工厂模式”,做一下修改:

        我们使用static修饰,更改方法名,通过不同的方法名获取类,在方法里new一个类,里面设置一些参数,再返回这个类,如下图所示:

        这样的类,就称为工厂类,工厂类里面得到类的方法就称为工厂方法。

        总的来说,通过静态方法来封装new操作,在这个静态方法设置不同的属性,构造对象的过程,就称为工厂模式。

        5、拒绝策略(RejectExecutionHandler handler)

        该参数是上述部分参数中最重要的一个;

        在线程池中有一个阻塞队列,且该队列容纳线程数量有限,如果这个任务队列满了,这时有往线程池中添加任务,这时候线程池要学会拒绝,由拒绝策略,在java标准库中就提供了以下四种拒绝策略,如下图所示:

        拒绝策略讲解:

        第一个策略:会直接抛出一个异常,这样,旧的任务执行不了,新的任务也执行不了

        第二个策略:把新的任务丢给添加任务队列的线程执行,不给入队列,同时旧的任务依然在执行

        第三个策略:把最旧的任务丢弃,添加最新的任务进来

        第四个策略:直接把新的任务丢弃了,不执行新的任务,旧的任务会继续执行

2.2 Executors类 

        ThreadPoolExecutor类本身使用起来比较复杂,java标准库给我们提供了另一个版本:把ThreadPoolExecutor封装了一下,这个类就是Executor工厂类,通过这个类创建出不同的线程池对象,在其内部,已经把ThreadPoolExecutor创建好了,并且设置了一些参数。

        Executor的简单使用,其中主要方法有一下4个,如图:

        eg:我们使用newFixedThreadPool(4)方法创建4固定个线程数目的线程池,再往里添加任务:

package thread;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadDemo32 {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("smallye");}});}
}

        结果如下:

        至于如何确定使用Executor或ThreadPoolExecutor,主要是看具体的情况;

2.3 线程池的执行流程

        主要有以下四个情况:
        1、当有任务要让线程池里面的线程执行时,会比较工作线程数和核心线程数,                     如果工作线程数 < 核心线程数,则会直接安排线程去执行这个任务。

        2、当工作线程数 > 核心线程数,即线程池中的核心线程数满了,会添加进阻塞任务队列中,添加任务队列前也会判断任务队列是不是空,是空就阻塞等待。

        3、如果线程池中的存活线程数 == 核心线程数,并且阻塞任务队列也满了,此时会判断是否到了最大线程数:maximumPoolSize,如果没有到达,就会让非核心线程去执行这个任务。

        4、如果当前线程数到达了最大线程数,则会执行拒绝策略

2.4 关于线程池中创建多少线程

        这是我们就需要关注该进程是cpu密集型还是io密集型;
        假设一个进程中,所有线程都是cpu密集型,这时每个线程的工作都是在cpu上执行的,此时,线程池中的数目就不应该超过N(cpu的逻辑核心线程数)

        假设一个进程中,所有线程都是IO密集型的,这时每个线程的大部分工作都是在等待IO,此时,线程池中的数目就可以远远超过N(cpu的逻辑核心线程数)

        实际上一个进程中的线程,有cpu密集型的,也有IO密集型的,只是比例不同。由于程序的复杂性,很难直接对线程池进行预估,更准确的做法是通过实验 / 测试的方法,找出合适的线程数目;

3. 线程池的模拟实现

        我们写代码实现一个简单的线程池:(直接写一个固定线程数目的线程池-->暂时不考虑线程的增加和减少),其中具体思路主要一下步骤:

  1. 提供构造方法,指定创建多少个线程池
  2. 在构造方法中,把这些线程都创建好
  3. 有一个阻塞队列,能够持有要执行的任务
  4. 提供submit方法,可以添加新的执行任务;

3.1 阻塞队列--->存放要执行的任务

// 就是一个用来保存任务的队列.private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

3.2 submit方法--->添加任务的方法,任务添加到队列中

  public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}

3.3 构造方法--->指定创建多少个线程,线程在这个构造方法中都创建好了

// 通过 n 指定创建多少个线程public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {// 线程要做的事情就是把任务队列中的任务不停的取出来, 并且进行执行while (true) {try {// 此处的 take 带有阻塞功能的.// 如果队列为 空, 此处的 take 就会阻塞.Runnable runnable = queue.take();// 取出一个任务就执行一个任务即可runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();threadList.add(t);}}

        线程里面,取出一个任务就执行这个任务,如果队列里没有任务,就会阻塞等待,等有任务,再执行任务,如此循环往复;每创建一个线程,都要放进链表中,也要记得start,开启线程。

3.4 存放线程的链表--->每创建一个线程都放进链表中,这样也能让我们找到某个线程 

//存放线程的链表
List<Thread> list = new ArrayList<>();

3.5 完整版代码

class MyThreadPoolExecutor {private List<Thread> threadList = new ArrayList<>();// 就是一个用来保存任务的队列.private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);// 通过 n 指定创建多少个线程public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {// 线程要做的事情就是把任务队列中的任务不停的取出来, 并且进行执行while (true) {try {// 此处的 take 带有阻塞功能的.// 如果队列为 空, 此处的 take 就会阻塞.Runnable runnable = queue.take();// 取出一个任务就执行一个任务即可runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();threadList.add(t);}}public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}
}public class ThreadDemo33 {
//指定线程池的数目为4个线程,添加1000次任务到阻塞队列中,
//让着4个线程从阻塞队列中拿任务,再执行任务
//任务:打印0~1000,并显示是哪个线程打印的;public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);for (int i = 0; i < 1000; i++) {int n = i;executor.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务" + n + " , 当前线程为: " + Thread.currentThread().getName());}});}}
}

        结果如下:

ps:关于线程池案例的内容就到这里了,如果大家感兴趣的话,就请一键三连哦!!!

相关文章:

【Java EE初阶九】多线程案例(线程池)

一、线程池的引入 引入池---->主要是为了提高效率&#xff1b; 最开始&#xff0c;进程可以解决并发编程的问题&#xff0c;但是代价有点大了&#xff0c;于是引入了 “轻量级进程” ---->线程 线程也能解决并发编程的问题&#xff0c;而且线程的开销比进程要小的多&…...

理解 Node.js 中的事件循环

你已经使用 Node.js 一段时间了&#xff0c;构建了一些应用程序&#xff0c;尝试了不同的模块&#xff0c;甚至对异步编程感到很舒适。但是有些事情一直在困扰着你——事件循环&#xff08;Event Loop&#xff09;。 如果你像我一样&#xff0c;花费了无数个小时阅读文档和观看…...

Mac 软件出现「意外退出」及「打不开」解决方法

Mac 软件出现「意外退出」及「打不开」解决方法 软件出现意外退出及软件损坏的情况&#xff0c;这是因为苹果删除了TNT的证书&#xff0c;所以大部分TNT破解的Mac软件会出现无法打开&#xff0c;提示意外退出。 终端需先安装Xcode或Apple命令行工具 如未装Xcode可以使用下列命…...

随机森林 3(代码)

通过随机森林 1和随机森林 2 的介绍&#xff0c;相信大家对理论已经了解的很透彻&#xff0c;接下来带大家敲一下代码&#xff0c;不懂得可以加我入群讨论。 第一份代码是比较原始的代码&#xff0c;第二份代码是第一段代码中引用的primitive_plot&#xff0c;第三份代码是使用…...

勒索事件急剧增长,亚信安全发布《勒索家族和勒索事件监控报告》

近期(12.15-12.21)态势快速感知 近期全球共发生了247起攻击和勒索事件&#xff0c;勒索事件数量急剧增长。 近期需要重点关注的除了仍然流行的勒索家族lockbit3以外&#xff0c;还有本周top1勒索组织toufan。toufan是一个新兴勒索组织&#xff0c;本周共发起了108起勒索攻击&a…...

LeetCode1523. Count Odd Numbers in an Interval Range

文章目录 一、题目二、题解 一、题目 Given two non-negative integers low and high. Return the count of odd numbers between low and high (inclusive). Example 1: Input: low 3, high 7 Output: 3 Explanation: The odd numbers between 3 and 7 are [3,5,7]. Exam…...

E中国铜金属行业需求前景及未来发展机遇分析报告2024-2030年

E中国铜金属行业需求前景及未来发展机遇分析报告2024-2030年 &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 《报告编号》: BG471816 《出…...

python SVM 保存和加载模型参数

在 Python 中&#xff0c;你可以使用 scikit-learn 库中的 joblib 或 pickle 模块来保存和加载 SVM 模型的参数。以下是一个简单的示例代码&#xff0c;演示了如何使用 joblib 模块保存和加载 SVM 模型的参数&#xff1a; 保存模型参数&#xff1a; from sklearn import svm …...

JAVA进化史: JDK12特性及说明

JDK 12于2019年3月发布。这个版本相对于之前的版本来说规模较小&#xff0c;主要集中在一些改进和实验性的特性上。以下是JDK 12的一些主要特性&#xff1a; 引入了实验性的Shenandoah垃圾收集器 JDK 12引入了实验性的Shenandoah垃圾收集器&#xff0c;旨在实现极低的暂停时间…...

Databend 的算力可扩展性

作者&#xff1a;尚卓燃&#xff08;PsiACE&#xff09; 澳门科技大学在读硕士&#xff0c;Databend 研发工程师实习生 Apache OpenDAL(Incubating) Committer PsiACE (Chojan Shang) GitHub 对于大规模分布式数据处理系统&#xff0c;为了更好应对数据、流量、和复杂性的增长…...

「解析」Windows 如何优雅使用 Terminal

所谓工欲善其事必先利其器&#xff0c;对于开发人员 Linux可能是首选&#xff0c;但是在家学习的时候&#xff0c;我还是更喜欢使用 Windows系统&#xff0c;首先是稳定&#xff0c;其次是习惯了。当然了&#xff0c;我还有一台专门安装 Linux系统的小主机用于学习Linux使用&am…...

Linux第18步_安装“Ubuntu系统下的C语言编译器GCC”

Ubuntu系统没有提供C/C的编译环境&#xff0c;因此还需要手动安装build-essential软件包&#xff0c;它包含了 GNU 编辑器&#xff0c;GNU 调试器&#xff0c;和其他编译软件所必需的开发库和工具。本节用于重点介绍安装“Ubuntu系统下的C语言编译器&#xff27;&#xff23;&a…...

【Linux】Linux 基础命令 crontab命令

1.crontab命令 crond 是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务 工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动…...

14:00面试,14:08就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到10月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40…...

Ubuntu envs setting

1. change the chmod of folders sudo chown -R $USER:$USER /home/anaconda3 2. torch.cuda.is_available()返回false change conda installation to pip. zai qi ta huan jing pei zhi dou mei wen ti de qing kuang xia , zai shi shi zhe ge fang fa. # CUDA 11.7 con…...

Windows 下用 C++ 调用 Python

文章目录 Part.I IntroductionChap.I InformationChap.II 预备知识 Part.II 语法Chap.I PyRun_SimpleStringChap.II C / Python 变量之间的相互转换 Part.III 实例Chap.I 文件内容Chap.II 基于 Visual Studio IDEChap.III 基于 cmakeChap.IV 运行结果 Part.IV 可能出现的问题Ch…...

九州金榜|家庭教育一招孩子不在任性

有一次和朋友一块聚餐&#xff0c;邻座是一位妈妈、和她大概七八岁的儿子&#xff0c;小男孩长得很帅气&#xff0c;没有像同龄人那样调皮捣乱&#xff0c;而是和妈妈很温馨的就餐。 看的出来一家人的素质很高&#xff0c;就餐过程中桌面保持的很整洁&#xff0c;交流声音也不…...

爬虫案列 --抖音视频批量爬取

""" 项目名称: 唯品会商品数据爬取 项目描述: 通过requests框架获取网页数据 项目环境: pycharm && python3.8 作者所属: 几许1. 对主页抓包 , 鼠标移动到视频位置视频自动播放获得视频数据包 2. 对视频数据包地址进行解析 , 复制链接 , 进行检索 3. 获…...

【React系列】React中的CSS

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. React中的css方案 1.1. react 中的 css 事实上&#xff0c;css 一直是 React 的痛点&#xff0c;也是被很多开发…...

基于Kettle开发的web版数据集成开源工具(data-integration)-应用篇

目录 &#x1f4da;第一章 基本流程梳理&#x1f4d7;页面基本操作&#x1f4d7;对应后台服务流程 &#x1f4da;第二章 二开思路&#x1f4d7;前端&#x1f4d7;后端 &#x1f53c;上一集&#xff1a;基于Kettle开发的web版数据集成开源工具(data-integration)-介绍篇 *️⃣主…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释

以Module Federation 插件详为例&#xff0c;Webpack.config.js它可能的配置和含义如下&#xff1a; 前言 Module Federation 的Webpack.config.js核心配置包括&#xff1a; name filename&#xff08;定义应用标识&#xff09; remotes&#xff08;引用远程模块&#xff0…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...