多线程(初阶九:线程池)

目录

一、线程池的由来

二、线程池的简单介绍

1、ThreadPoolExecutor类

(1)核心线程数和最大线程数:

(2)保持存活时间和存活时间的单位

(3)放任务的队列

(4)线程工厂

(5)拒绝策略

2、Executors类

3、线程池的执行流程

4、讨论线程池中创建多少线程合适

三、线程池的模拟实现

(1)阻塞队列:存放要执行的任务

(2)submit方法:添加任务的方法,任务添加到队列中

(3)构造方法:指定创建多少个线程,线程在这个构造方法中都创建好了

(4)存放线程的链表:每创建一个线程都放进链表中,这样也能让我们找到某个线程

(5)最终代码( + 测试用例)

都看到这了,点个赞再走吧,谢谢谢谢谢


一、线程池的由来

最开始,进程可以解决并发编程的问题,但是这个代价太大了,于是引入了 “轻量级进程” :线程

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

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

(1)引入 “轻量级线程”:纤程 / 协程

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

(2)引入 “线程池”

        线程池的概念:创建一个线程,这个线程执行完,不会把这个线程给销毁,而是把这个线程放到线程池中,当我们需要用这个线程的时候,再从线程池中拿,不需要的时候,就放在线程池中,并不会销毁它;这样,就省去了频繁的创建销毁线程了。

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

        举个栗子:

假设在银行场景中,滑稽老铁要去这个银行办理一个业务,一般银行中大堂有复印机;这时,滑稽老铁没有带身份证复印件,此时滑稽老铁要去搞到身份证复印件,有两个选择,其一选择:把身份证给柜员,让柜员帮滑稽老铁复印,但是这个操作是不可控的,可能这个柜员中途被老板安排了其他活,那这个时候,就不能帮滑稽老铁复印身份证了,要等忙完老板安排的活,再帮滑稽老铁复印身份证;其二选择:滑稽老铁自己去大堂中复印身份证,这样就比较可控了,滑稽老铁可以很快的去到打印机,立马复印出来,再去办理他的业务。如图:

这里的大堂就是用户态,柜台就是内核态,从线程池中取线程,是纯用户态代码(可控)                                                                                   通过系统申请创建线程,需要内核完成(不可控)


二、线程池的简单介绍

1、ThreadPoolExecutor类

ThreadPoolExecutor参数最多的构造方法,明白了这个构造方法,其他构造方法的参数也就都明白了,如图:

(1)核心线程数和最大线程数:

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

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

        举个栗子:一个公司中有10个正式员工,这10个正式员工是不能随便开除的,当这10个正式员工忙不过来的时候,公司为了降低成本,会招聘实习员工,而这几个实习员工是可以随便开除的,当公司稳定一段时间不忙后,就会开除几个实习员工。

(2)保持存活时间和存活时间的单位

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

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

(3)放任务的队列

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

(4)线程工厂

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

举个栗子:

描述一个点,可以用二维坐标和极坐标来表示:二维坐标:(x,y) 极坐标:(r,α)

这里,通过new一个类来得到一个点,这个类里有两个构造方法,参数分别是(double x,double y),(double r,double α),那么这两个构造方法的参数类型都一样,构成不了重载,如图:

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

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

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

(5)拒绝策略

在线程池中有一个阻塞队列,这个队列容纳线程有上限,如果这个任务队列满了,这时有往再添加任务,会发生啥事?

这就引出了拒绝策略,在线程池中,会有四个拒绝策略,如图:

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

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

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

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

2、Executors类

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

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

我们创建一个固定线程数目的线程池,再往里添加任务

代码:

public class ThreadPoolTest {public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello word");}});}
}

执行结果:

那啥时候使用Executor,啥时候使用ThreadPoolExecutor呢?

网上流传了 阿里巴巴java开发编程规范,里面写了不建议使用Executor,而且一定要使用ThreadPoolExecutor,里面说用ThreadPoolExecutor意味着一切都在掌控之中,可以避免一些不必要的因素;我们可以作为参考,不必奉为金科玉律,他们两各有各的优缺点,这也要以以后入职的公司编程规范为准。

3、线程池的执行流程

(1)当有有任务要让线程池里面的线程执行时,会比较工作线程数和核心线程数,                     如果工作线程数 < 核心线程数,则会直接安排线程去执行这个任务。

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

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

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

4、讨论线程池中创建多少线程合适

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

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

上述情况都是极端情况,实际上一个进程中的线程,有cpu密集型的,也有IO密集型的,只是比例不同。由于程序的复杂性,很难直接对线程池进行预估,更准确的做法是通过实验 / 测试的方法,找出合适的线程数目;也就是尝试给线程池设定不同的线程,对不同线程情况线程池执行的效率、性能进行评估,找到合适的线程数目。


三、线程池的模拟实现

模拟线程数目固定的线程池

(1)阻塞队列:存放要执行的任务

代码:

//阻塞队列:存放要执行的任务
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);

(2)submit方法:添加任务的方法,任务添加到队列中

代码:

//提供submit方法,可以添加任务
public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);
}

(3)构造方法:指定创建多少个线程,线程在这个构造方法中都创建好了

public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {//取出一个任务Runnable runnable = queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();list.add(t);}}

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

(4)存放线程的链表:每创建一个线程都放进链表中,这样也能让我们找到某个线程

代码:

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

(5)最终代码( + 测试用例)

class MyThreadPoolExecutor {//存放线程的链表List<Thread> list = new ArrayList<>();//阻塞队列:存放要执行的任务private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);//提供submit方法,可以添加任务public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {//取出一个任务Runnable runnable = queue.take();//执行任务runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();list.add(t);}}
}
public class MyThreadPoolExecutorTest {public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(4);for (int i = 0; i < 1000; i++) {//变量捕获int n = i;myThreadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务:" + n + ",当前线程:" + Thread.currentThread().getName());}});}}
}

测试用例:指定线程池的数目为4个线程,添加1000次任务到阻塞队列中,让着4个线程从阻塞队列中拿任务,再执行任务,任务:打印0~1000,并显示是哪个线程打印的;

注意:这里我们打印那里我们不能直接放 i ,这里涉及到变量捕获,不能编译通过,但他们可以在循环里创建一个变量,把 i 的值赋值给这个变量,再打印 n,这样每循环一次,都会创建一个成员变量,这个成员变量也不会变,预期也和我们想要预期效果一样。

执行结果,如图:

可以看到,并不是顺序打印1~1000的,因为不同线程拿到任务的时机不同,多线程执行的顺序也是随机的。


都看到这了,点个赞再走吧,谢谢谢谢谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/215030.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Axure的安装以及简单使用

目录 Axure简介 是什么 有什么用 Axure的优缺点 优点&#xff1a; 缺点&#xff1a; 安装 汉化 Axure的使用 工具栏 页面 ​编辑 添加子页面 ​编辑 Axure简介 是什么 Axure是一款著名的原型设计工具。它允许用户创建交互式线框图、流程图、原型和其他设计文档&…

「Verilog学习笔记」脉冲同步电路

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1nsmodule pulse_detect(input clk_fast , input clk_slow , input rst_n ,input data_in ,output dataout );reg data_level, dat…

第十一章 React 封装自定义组件

一、专栏介绍 &#x1f30d;&#x1f30d; 欢迎加入本专栏&#xff01;本专栏将引领您快速上手React&#xff0c;让我们一起放弃放弃的念头&#xff0c;开始学习之旅吧&#xff01;我们将从搭建React项目开始&#xff0c;逐步深入讲解最核心的hooks&#xff0c;以及React路由、…

【NLP】RAG 应用中的调优策略

​ 检索增强生成应用程序的调优策略 没有一种放之四海而皆准的算法能够最好地解决所有问题。 本文通过数据科学家的视角审视检索增强生成&#xff08;RAG&#xff09;管道。它讨论了您可以尝试提高 RAG 管道性能的潜在“超参数”。与深度学习中的实验类似&#xff0c;例如&am…

axios 基础的 一次封装 二次封装

一、平常axios的请求发送方式 修改起来麻烦的一批 代码一大串 二、axios的一次封装 我们会在src/utils创建一个request.js的文件来存放我们的基地址与拦截器 /* 封装axios用于发送请求 */ import axios from axios/* (1)request 相当于 Axios 的实例对象 (2)为什么要有reque…

VSCode使用Remote-SSH连接服务器时报错:无法与“***”建立连接: XHR failed.

关于VSCode的报错问题&#xff1a;无法与“***”建立连接: XHR failed 问题描述问题理解解决方法手动在本地下载安装包&#xff0c;然后手动传到服务器端 问题描述 是的&#xff0c;我又踩坑了&#xff0c;而且这个弄了好久&#xff0c;也重新装了VSCode软件&#xff0c;好像结…

前端框架(Front-end Framework)和库(Library)的区别

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

阿里云国际版CDN加速,如何判断网站IP已加速?

将源站接入阿里云CDN服务后&#xff0c;您可以通过IP检测功能&#xff0c;检测客户端请求实际访问的IP是否为CDN加速节点IP&#xff0c;判断加速是否生效。 应用场景 IP检测的应用场景如下&#xff1a; 场景一&#xff1a;成功配置CDN后&#xff0c;您可以检测客户端请求实际…

Postman高级应用——变量、流程控制、调试、公共函数、外部数据文件

Postman 提供了四种类型的变量 环境变量&#xff08;Environment Variable&#xff09; 不同的环境&#xff0c;使用不同的环境变量&#xff0c;例如&#xff1a;测试过程中经常会用到 测试环境&#xff0c;外网环境等 全局变量&#xff08;Global Variable&#xff09; 所有的…

【计算机网络】序列化,反序列化和初识协议

目录 ​编辑 一、概念 二、 序列化过程&#xff1a; 选择序列化格式&#xff1a; 实现序列化代码&#xff1a; JSON示例&#xff1a; Protocol Buffers示例&#xff1a; JSON编码示例&#xff1a; 传输或存储&#xff1a; 三、反序列化过程&#xff1a; 下面是反序列…

(企业 / 公司项目) 企业项目如何使用jwt?

按照企业的项目然后写的小demo&#xff0c; 自己搞一个登录接口然后调用jwtUtil工具类 后端实现 创建一个通用模块common来实现jwt生成token 登录注册的基本实现逻辑思路 面试| ProcessOn免费在线作图,在线流程图,在线思维导图 注释挺详细的jwtUtil工具类&#xff0c; 封装的…

WPF仿网易云搭建笔记(5):信息流控制之IOC容器

文章目录 专栏和Gitee仓库前言IOC容器Prism IOC使用声明两个测试的服务类MainWindow IOC 注入[单例]MainWindow里面获取UserController无法使用官方解决方案 使用自定义IOC容器&#xff0c;完美解决既然Prism不好用&#xff0c;直接上微软的IOC解决方案App.xaml.csViewModel里面…

网络测试工具:tcping-测试端口连接

网络测试工具&#xff1a;tcping-测试端口连接 平常使用的ping&#xff0c;是通过icmp协议去测试网络连通性的&#xff0c;tcping是通过tcp三次握手测试端口的连通性。总的来说&#xff0c;ping测试的是L3的连通性&#xff0c;tcping测试的是L4的连通性。 tcping工具下载 htt…

10.RIP路由信息协议

10.RIP 网段经常产生变化的话&#xff0c;建议使用动态路由协议&#xff0c;当网段发生变化的时候会自动通告给其他路由器 它不看链路的带宽&#xff0c;只看链路中的跳数&#xff0c;只要是跳数多的&#xff0c;不管带宽有多大&#xff0c;它就认为是不好的 RIP跳数有限 …

TYPE-C接口设备实现DRP+OTG功能芯片

随着USB-C接口的普及&#xff0c;欧盟的法律法规强制越来越多的设备开始采用这种接口。由于 USB-C接口的高效性和便携性&#xff0c;使各种设备之间的连接和数据传输变得非常方便快捷&#xff0c;它们不仅提供了强大的功能&#xff0c;还为我们的日常生活和工作带来了极大的便利…

青少年CTF-Misc(持续更新中)

FLAG&#xff1a;当觉得自己很菜的时候&#xff0c;就静下心来学习 专研方向:Web安全&#xff0c;CTF 每日emo&#xff1a;听一千遍反方向的钟&#xff0c;我们能回到过去吗&#xff1f; 1.StegoTXT&#xff1a; 解压缩文件。发现字母中存在覆盖。使用0宽隐写在线解密得到flag…

YOLOv8改进 | 2023主干篇 | EfficientViT替换Backbone(高效的视觉变换网络)

一、本文介绍 本文给大家带来的改进机制是EfficientViT&#xff08;高效的视觉变换网络&#xff09;&#xff0c;EfficientViT的核心是一种轻量级的多尺度线性注意力模块&#xff0c;能够在只使用硬件高效操作的情况下实现全局感受野和多尺度学习。本文带来是2023年的最新版本…

量子算力引领未来!玻色量子出席第二届CCF量子计算大会

​8月19日至20日&#xff0c;中国计算机学会&#xff08;CCF&#xff09;主办的第二届CCF量子计算大会暨中国量子计算峰会&#xff08;CQCC 2023&#xff09;在中国合肥成功举办。本届大会以“量超融合&#xff0c;大国算力”为主题&#xff0c;设有量子计算软件、硬件、应用生…

计算机网络(三)

&#xff08;十一&#xff09;路由算法 A、路由算法分类 动态路由和静态路由 静态路由&#xff1a;人工配制&#xff0c;路由信息更新慢&#xff0c;优先级高。这种在实际网络中要投入成本大&#xff0c;准确但是可行性弱。 动态路由&#xff1a;路由更新快&#xff0c;自动…

12/11

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#xf…