初始JavaEE篇——多线程(6):线程池

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程程(ಥ_ಥ)-CSDN博客

所属专栏:JavaEE

到现在为止,我们已经学习了两个经典的多线程案例了:饿汉模式与懒汉模式、阻塞队列与生产者—消费者模型。想要详细了解的小伙伴,可以去看往期文章。现在我们来学习另外一个案例:线程池。

目录

线程池

Java标准库中的线程池

参数解析

使用线程池

模拟实现线程池


线程池

概念:线程池,简单理解就是 一块内存中存放着多个线程。

作用:高效的创建与销毁线程。

举个例子来理解:

在一个阳光明媚的下午,高小强想吃鱼了,于是就告诉老莫,我想吃鱼了,老莫也是心领神会地跑去买鱼,但是很不幸的是高小强家里离买鱼的地方很远,所以老莫每次都得跑很远的路去买回来,一次两次还好,主要是高小强老是想着吃鱼,所以老莫也很是辛苦,因此老莫便想了个法子:直接在高小强家附近搞了个鱼塘,这样每次高小强想要吃鱼了,就可以直接下塘抓鱼,这样老莫也就轻松了不少。

在上面的例子中,老莫是充当CPU与操作系统的角色,而高小强是用户,高小强吃鱼这件事就是一个线程。当用户频繁地去创建与删除线程时,就会影响操作系统、CPU的效率(老莫只能经常去买鱼,而很少有时间去干自己想干的事情)。因此老莫这里的操作是建了一个鱼塘,对应计算机中的操作就是创建一个线程池。

线程池的工作原理:当外界有任务时,线程池的所在的线程就会随机启动其中的一个线程去执行任务,当任务执行完成时,这个线程并不会被销毁,而是重新回到线程池中等待下一次任务来临时,等待被调度。这就省下了创建线程与销毁线程所消耗资源。这里的资源主要是指CPU从用户态变为核心态以及操作系统切换到内核区工作。

Java标准库中的线程池

我们主要是要知道然后去创建并使用Java标准库中的线程池。

提供的类是:ThreadPoolExecutor。

参数解析

上面是这个类的构造方法,从上到下参数的个数是增多的。我们是要清楚构造方法的全部参数的。

下面是对最详细版的构造方法的参数解释: 

注意:

1、 只有当核心线程数全部在工作时,如果这时需要处理新的任务,才会去创建新的线程。就好比一个公司,当内部员工足以处理这些业务时,就没必要花钱请外包,但是当内部员工已经忙不过来时,这时候才会需要外包来干新的任务。

2、当公司的业务过了旺季,到了淡季,这时候公司内部的员工都可能是出于空闲的状态,那外部更加没事干,因此公司就会考虑和外包解除合同。

3、线程工厂,这里使用了一种设计模式:工厂模式,其与我们前面学习的单例模式是出于同一级别的。工厂模式主要弥补构造方法的缺陷。例如,现在有一个类是用来描述平面直角坐标系中的一个点,描述的方式有两种:1、使用 (x,y) 坐标的方式;2、使用极坐标(用三角函数来实现)的方式;

因此,解决这样的问题,我们就可以使用工厂模式,将构造方法改为使用静态的方法,这样最终就不用通过构造方法来实现了。

代码演示:

class Point {private double x = 0;private double y = 0;// 1、使用(x,y)的方式public Point(double x, double y) {this.x = x;this.y = y;}// 2、使用极坐标的方式/*public Point(double r, double a) {this.x = r * Math.cos(a);this.y = r * Math.cos(a);}*/public static Point getInstanceByXY(double x, double y) {return new Point(x,y);}public static Point getInstanceByRA(double r, double a) {return new Point(r*Math.cos(a), r*Math.sin(a));}
}

4、拒绝策略:

对于这四种拒绝策略,1、3、4 应该是很好理解的,但对于第二种来说,可能有点模糊。第二种方式,是告诉需要执行任务的线程:这个任务,我现在没空,你自己去把这个任务完成吧。然后需要执行这个任务的线程,就会自己把这个任务执行完。

我们先来了解一下,任务是什么?通过前面的学习,我们已经知道了,线程就是轻量级进程,也就是一段需要执行的指令。任务同样也可以看作是一段需要执行的指令,并且任务所需要执行的代码都是用 Runnable给包装起来的。给线程池去执行的话,就是让线程池对象调用 submit 方法,然后把包含任务代码的Runnable给作为参数扔给 sumbit 去执行。这就是线程池执行任务的过程。

 从上面的分析,我们也可以得出一个结论:交给线程池执行的任务,而让自己(需要执行该任务的线程)执行任务 的区别在于:线程池会利用多线程的方式去执行该任务,而自己只会去串行执行,这样就影响了程序最终的效率。而让自己去执行,其实就是底层让 Runnable 去调用 run 方法。

1)有小伙伴可能会对 3、4有疑惑:丢弃最新的任务和让需要执行该任务的线程自己去执行 的区别是不是前者根本就没有线程去执行,而后者是调用submit的线程(也就是需要执行该任务的线程)去执行。

2)也有小伙伴可能会遇到这种说法:4 是丢弃当前任务。这种说法也是正确的,这里最新的任务和当前的任务都是指需要被执行的任务。当前任务不就是需要被执行的任务嘛,最新的任务不也是需要被执行的任务嘛,对叭,细细品味一下。

使用线程池

上面就是对构造方法的参数的解析,下面我们就来使用一下这个线程池。由于原本的类参数过多,因此JVM又对其进行了部分封装,最终我们使用的类是 ExecutorSever 。

public class Test {public static void main(String[] args) throws InterruptedException {// 创建一个线程数目固定的线程池ExecutorService service = Executors.newFixedThreadPool(2);// 创建一个很大的线程池,最大线程的数目是Integer.MAX_VALUE// ExecutorService service = Executors.newCachedThreadPool();        for (int i = 0; i < 10; i++) {int id = i;// 创建任务Runnable task = new Runnable() {@Overridepublic void run() {System.out.println("Hello task"+id+"--->"+Thread.currentThread().getName());}};// 调用线程池中的线程执行任务service.submit(task);Thread.sleep(1000);}}
}

我们去运行就会发现,上面的任务执行完成后,也就是所有的代码全部执行完成后,进程没有停下,还在继续。这是因为 线程池中的线程是前台线程的。

我们也可以去看这两个类的源码:

注意:上述代码的打印语句中不能使用 i ,因为在匿名内部类中,访问外部类的局部变量,采用的是变量捕获的方式,而这个方式固定了我们访问的变量必须是 final修饰的或者是事实 final(和我们上面一样,虽然没有用 final 修饰,但是最终的值并没有发生变化),i 在创建之后,还进行了 i++ 的操作,使得其发生了变化。

模拟实现线程池

接下来,我们就来模拟实现一个简单的线程池。

要求:与我们使用的线程池的效果要大致一样。

思路:实现线程池主要是要实现其中的 submit 方法,与构造方法。

模拟实现代码:

public class MyThreadPool {// 定义一个阻塞队列,来接收要处理的任务private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);public MyThreadPool() {this(10);}public MyThreadPool(int nThread) {// 分配好线程数for (int i = 0; i < nThread; i++) {// 创建线程Thread t = new Thread(()->{// 要执行的任务 ——> 工作队列中找try {while (true) { // 执行完成之后,不能让这个线程销毁(run方法执行完,线程就销毁了)Runnable task = queue.take(); // 为空,内部会阻塞等待task.run(); // 执行任务}} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}}public void submit(Runnable task){// 把任务给到submit,然后由其来执行try {queue.put(task); // 为满,内部会阻塞等待} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

测试代码:

public class Test {public static void main(String[] args) {// 1、创建线程池MyThreadPool threadPool = new MyThreadPool(2);// 2、创建任务并执行for (int i = 0; i < 10; i++) {int id = i;// 2.1 创建任务Runnable task = new Runnable() {@Overridepublic void run() {System.out.println("Hello task"+id+"--->"+Thread.currentThread().getName());}};// 2.2 执行任务threadPool.submit(task);}}
}

注意:

1、我们手动创建的线程是属于前台线程。 

2、

1)怎么样保证线程不会销毁(还在执行的过程中) —— 设置为前台线程。

2)怎么样保证线程在处于空闲状态,且不会被销毁 —— 只有线程处于 run方法中,那么线程就不会被销毁,即在run方法中搞一个死循环即可。并且当工作队列为空时,线程会阻塞等待在 take 方法。

那如果我们实在是想要线程池中的线程在执行完任务之后,就销毁呢? 

在我们自己实现的线程池中,只需要把创建的线程设为后台线程即可,而在Java提供的类中,我们需要用到 shutdown ,这个方法是无脑关闭,即使是有没有执行完成的任务,也会关闭。因此面对这种情况,就需要用到另一个方法:awaitTermination,这个方法是用来阻塞关闭线程池的,当线程池中任务没有执行全部完时,便会去等待我们手动设置的超时时间,(在这个超时时间之内)这个方法便会阻塞关闭线程池的线程,因此,上面两个方法一般都是连在一起用的。而如果线程池中任务已经全部执行完毕,就不会发生阻塞等待的情况。简单来说,就是检查有没有全部执行完成,如果执行完成了,就直接往下走,如果还存在没有执行完的,就会阻塞等待这个超时时间段,当超过这个时间段了,即使还没有全部执行完,此时也会往下走。

代码演示:

public class Test {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(2);for (int i = 0; i < 10; i++) {executorService.submit(() -> {try {Thread.sleep(500);System.out.println("任务执行完成");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}try {// 当线程池中还有任务没有执行完,就会阻塞等待 3s;反之,则直接往下执行boolean terminated = executorService.awaitTermination(3000, TimeUnit.MILLISECONDS);if (terminated) { // 当没有触发阻塞等待,就会返回true;反之,则返回falseSystem.out.println("所有的任务都已经执行完了");} else {System.out.println("存在部分任务没有执行完");}} catch (InterruptedException e) {executorService.shutdownNow();Thread.currentThread().interrupt();}executorService.shutdown(); // 这个代码就是线程池中还存在没有执行的任务}
}

运行结果:

好啦!本期  初始JavaEE篇——多线程(6):线程池 的学习之旅到此结束啦!我们下一期再一起学习吧!

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

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

相关文章

static、 静态导入、成员变量的初始化、单例模式、final 常量(Content)、嵌套类、局部类、抽象类、接口、Lambda、方法引用

static static 常用来修饰类的成员&#xff1a;成员变量、方法、嵌套类 成员变量 被static修饰&#xff1a;类变量、成员变量、静态字段 在程序中只占用一段固定的内存&#xff08;存储在方法区&#xff09;&#xff0c;所有对象共享可以通过实例、类访问 (一般用类名访问和修…

CI/CD 的原理

一、CI/CD 的概念 CI/CD是一种软件开发流程&#xff0c;旨在通过自动化和持续的集成、测试和交付实现高质量的软件产品。 CI(Continuous Integration)持续集成 目前主流的开发方式是协同开发&#xff0c;即多位开发人员同事处理同意应用不同模块或功能。 如果企业在同一时间将…

高效数据集成案例:从聚水潭·奇门到MySQL

聚水潭奇门数据集成到MySQL的技术案例分享 在企业信息化建设中&#xff0c;数据集成是实现业务流程自动化和数据统一管理的关键环节。本文将分享一个具体的系统对接集成案例&#xff1a;如何将聚水潭奇门平台上的销售出库单数据高效、可靠地集成到MySQL数据库中&#xff0c;以…

编译,链接,加载

编译、链接、加载 编译、链接、加载是基础&#xff0c;十几年前从《深入理解计算机系统》等相关书籍中获得了比较全面的理解&#xff0c;现在已经变得有些模糊了。当时没有做总结的习惯&#xff0c;现在零零散散的记一些吧&#xff0c;有时间还要重温书本。 Build time 编译器…

Python(pandas库3)

函数 随机抽样 语法&#xff1a; n&#xff1a;要抽取的行数 frac&#xff1a;抽取的比例&#xff0c;比如 frac0.5&#xff0c;代表抽取总体数据的50% axis&#xff1a;示在哪个方向上抽取数据(axis1 表示列/axis0 表示行) 案例&#xff1a; 输出结果都为随机抽取。 空…

YOLOv8实战野生动物识别

本文采用YOLOv8作为核心算法框架&#xff0c;结合PyQt5构建用户界面&#xff0c;使用Python3进行开发。YOLOv8以其高效的实时检测能力&#xff0c;在多个目标检测任务中展现出卓越性能。本研究针对野生动物数据集进行训练和优化&#xff0c;该数据集包含丰富的野生动物图像样本…

Semantic Kernel进阶:创建和管理聊天(ChatCompletion)历史记录对象(四)

一、引言 在构建基于Semantic Kernel的AI应用时&#xff0c;聊天历史记录对象&#xff08;Chat History&#xff09;是维护对话上下文和连续性的关键。本文将一步步指导你如何创建和管理聊天历史记录对象&#xff0c;从而为你的AI代理提供一个强大的对话管理工具。 二、聊天…

规划控制复现:Apollo LQR横向控制(C++/simulink实现)

本文在前文已经搭建好的ROS-C规划控制验证平台中进行LQR算法的复现&#xff0c;理论部分详见于&#xff1a; 规划控制复现&#xff1a;Apollo LQR横向控制&#xff08;算法原理推导与流程&#xff09;_apollo 规划控制-CSDN博客 Prescan-Carsim-ROS的仿真平台搭建详见于&…

ASO优化秘籍!

根据 App Annie 发布的报告显示&#xff0c;现全球移动设备使用时长达到新高&#xff0c;日均 4.2 小时。在这大环境下&#xff0c;App 的竞争也愈演愈烈&#xff0c;App 想让更多人看到&#xff0c;似乎越来越难。那么&#xff0c;App 可以通过哪些方式进行推广&#xff1f; …

面向对象编程中类与类之间的关系(一)

目录 1.引言 2."有一个"关系 3."是一个"关系(继承) 4.“有一个”与“是一个”的区别 5.not-a关系 6.层次结构 7.多重继承 8.混入类 1.引言 作为程序员&#xff0c;必然会遇到这样的情况&#xff1a;不同的类具有共同的特征&#xff0c;至少看起来彼…

React写关键字高亮的三个方案

1.js正则replaceAlldangerouslySetInnerHTML{{ __html: xxx }}危险属性 步骤最简单,但是是危险属性,不推荐使用,项目中实在没有头绪,可以使用它应急 通过useMemo计算得到新的状态值,赋值给dangerouslySetInnerHTML属性的__html 关键代码: const [state1, setState1] useSt…

Linux 生产者消费者模型

1. 背景概念 假设现在有多个线程&#xff0c;一部分线程负责生产任务&#xff0c;称为生产者productor&#xff0c;另一部线程负责执行任务&#xff0c;称为消费者consumer&#xff0c;他们之间是一对一一对一一对一的关系。 现在生产者productor-3有任务要派发&#xff0c;但…

PlantUML在IDEA中使用

1.打开settings,搜索PlantUML Integration并下载 2.安装并重启IDEA 3.学习相关的语法即可进行使用

Java之多线程的实现(创建)(3种实现方式)(面试高频)

目录 一、多线程的3种实现方式 &#xff08;1&#xff09;继承Thread类。 &#xff08;2&#xff09;实现Runnable接口。&#xff08;void run()&#xff1a;该方法无返回值、无法抛出异常&#xff09; &#xff08;3&#xff09;实现Callable接口。&#xff08;V call() throw…

企业如何吸引稀缺的高技能员工

高技能员工的稀缺性和招聘难度日益凸显&#xff0c;其原因主要在于技术发展迅速、人才供需失衡、企业竞争加剧。其中&#xff0c;技术发展迅速导致人才培养跟不上市场需求&#xff0c;使得高技能人才更加稀缺。以人工智能领域为例&#xff0c;新技术层出不穷&#xff0c;相关人…

【MySQL】MySQL数据库中密码加密和查询的解决方案

本篇博客是为了记录自己在遇到password函数无法生效时的解决方案。通过使用AES_ENCRYPT(str,key)和AES_DECRYPT(str,key)进行加密和解密。 一、问题 自己想创建一个user表&#xff0c;user表中有一个password属性列&#xff0c;自己想对密码进行加密后再存入数据库&#xff0c…

java质数的判断 C语言指针变量的使用

1. public static void main(String[] args) {Scanner scnew Scanner(System.in);System.out.println("请输入一个值");int num sc.nextInt();boolean flagtrue;for (int i2;i<num;i){if (num%i0){flagfalse;break;}}if (flag){System.out.println(num"是一…

Midjourney 3D:探索未来沉浸式体验的无限可能

一、Midjourney 3D:开启沉浸式新时代 最近,Midjourney宣布即将推出一款全新的3D产品,这不仅仅是一次简单的3D生成技术的升级,而是一场革命。这款新产品将基于先进的光场技术,而非传统的3D网格模型,为用户提供前所未有的沉浸式体验。用户不仅可以“跳入”生成的场景中自由…

CasPL: Cascade Prompt Learning for Vision-Language Model Adaptation

文章汇总 当前的问题 目前可学习的提示符号主要用于适应任务的单一阶段(即适应提示)&#xff0c;容易导致过度拟合风险。 动机 提示符将分两个阶段逐步优化。在初始阶段&#xff0c;学习增强提示&#xff0c;**通过使用大量未标记的领域图像数据对齐其预测逻辑&#xff0c;从…

【文献及模型、制图分享】基于投入品减量增效视角的长江经济带农业生产绿色化演进研究

文献介绍 绿色化转型是农业可持续发展研究的重要议题。以农业生产绿色化转型过程的理论分析为基础&#xff0c;运用文献调查、访谈与问卷调查、脱钩分析相结合的方法&#xff0c;研究了长江经济带农业生产绿色化转型过程和投入品减量增效的趋势。 结果表明&#xff1a; 2015…