(JavaEE)(多线程案例)线程池 (简单介绍了工厂模式)(含经典面试题ThreadPoolExector构造方法)

线程诞生的意义,是因为进程的创建/销毁,太重了(比较慢),虽然和进程比,线程更快了,但是如果进一步提高线程创建销毁的频率,线程的开销就不能忽视了。

这时候我们就要找一些其他的办法了。

有两种典型的办法可以进一步提高这里的效率:

1: 协程 (轻量级线程,相比于线程,把系统调度的过程给省略了,变成由程序员手工调度)

(当下,一种比较流行的并发编程的手段,但是在Java圈子里,协程还不够流行,GO和Python用的比较多)

2:线程池(Java用的)

接下来我们就来介绍一些线程池 

线程池

优化频繁创建销毁线程的场景 

首先,我们先来了解一下什么是池:

池 

假设一个美女,有很多人追,美女就从这些人里面挑了一个她最喜欢的交往,交往一段时间之后,美女她腻了,她想换一个男朋友,那她接下来就要干两件事

1:想办法和这个男的分手,需要一些技巧,比如挑他毛病,作 之类的

2:再找一个小哥哥,培养感情,然后交往 

但是这样的效率就比较低啦,有没有什么办法来提高一些效率呢?

当然有,只有美女在和前一个交往的时候,和另一个或多个小哥哥,搞暧昧(养🐟),把他们都放到自己的鱼塘里,那和上一个分手,下一个男朋友来的就很快了。 

我们还是只做了两步,只是把第二步提前了。 

“鱼塘”里的这些人,我们通常叫他们 —— “备胎” 

那这个“鱼塘” 就 可以看成 “池” ,来放“备胎”

同样的,线程池,就是在使用第一个线程的时候,提前把 2,3,4,5...(多个)线程创建好(相对于前面的培养感情),那后续我们想使用新的线程的时候,就不必重新创建了,直接拿里的线程用就行了。(此时创建线程的开销就被降低了)

为什么,从池子里取的效率比新创建线程效率高?

这是因为,从池子里取 这个动作,是存粹的 用户态 的操作,而创建新的线程,这个动作,则是需要 用户态 + 内核态 相互配合完成的操作。

内核态 和  用户态 

如果一段程序,是在系统内核中执行的,此时就称为“内核态” ,如果不是,则称为“用户态”

操作系统,是由 内核 + 配套的应用程序 构成的,

内核:系统最核心的部分

创建线程操作,就需要调用系统 api,进入到内核中,按照内核态的方式来完成一系列动作。

内核态的操作要比 纯用户态的操作开销要更大 :至于为什么,我们来举一个例子解释一下:

银行办业务的例子 

首先这个来办理业务的人他不能 进入柜台后面,只能在大厅里,

这个人想来办张银行卡,需要身份证复印件,但是这个人他忘带了,那此时柜台的服务人员就给了他两个选择:

1:把身份证给她,她去帮他复印

2:大厅的角落,有一个自助复印机,他可以去那里自己复印 

那这两个选择中的第二个,自己复印就是纯 用户态操作(这个人可以立即去复印,完事后立即回来办理业务,整个过程非常利落,非常可控

但是如果交给 柜台的服务人员(第一个选择),这个过程就涉及到 内核态 操作了,那此时,你把东西交给他俩,你也不知道柜员消失之后去做了那些事情,也不是的她啥时候回来,整个过程是不可控的。

操作系统内核,是要给所有的进程提供服务的,当你要创建线程的时候,内核虽然会帮你做,但是做的过程中难免也要做一些其他的事情。那在你这边的结果,就不是那么可控。

上述就是内核态 和 用户态的区别 。

Java标准库中的线程池
 

线程池的创建 

我们发现了,线程池这个对象不是我们直接 new 的,而是通过一个专门的方法,返回了一个线程池的对象。 

这种写法就涉及到了 “工厂模式”(校招常考的设计模式)(和上一篇介绍的 单例模式 并列) 

 工厂模式

工厂模式的作用? 

通常我们创建对象 都是使用 new,new 关键字就会触发 类的构造方法,但是构造方法,存在一定的局限性。

“工厂模式” 就是给 构造方法填坑的。 

 那 “工厂模式” 具体是填的什么 坑 呢,我们举一个例子:

 假设 考虑 一个类,来表示平面上的点

然后我们给这个类提供构造方法:

第一个构造方法: 

期待使用笛卡尔坐标系来构造对象。 

 

第二个构造方法:

使用极坐标来构造对象 

但是编译失败了。 

 

 原因:

很多时候,我们希望构造一个对象,可以有多种构造方式 。那多种方式,我们就需要使用多个版本的构造方法来分别实现,但是构造方法要求方法的名字必须是类名,不同的构造方法 只能通过 重载 的方式来区分了,而重载又要求 参数类型 或 个数 不同。

而上面的两个构造方法 很明显没有构成 重载,当然会编译失败。 

这就是 构造方法的局限性 。

“工厂模式”就能解决上述问题 :

使用普通的方法,代替构造方法完成初始化工作,普通的方法就可以使用方法的名字来区分了。也就不受 重载的规则制约了。

工厂模式实践 

在实践中,我们一般单独 搞一个类,然后给这个类搞一些静态方法,由这些静态方法负责构造出对象 

伪代码 

class PointFactory {public static Point makePointByXY(double x, double y) {Point point = new Point();point.setX(x);point.setY(y);return p;}public static Point makePointByRA(double r, double a) {//和上边类似}
} class Demo {public static void main(String[] args) {//使用 Point p = PointFactory.makePointByXY(10,20); }
}

上述介绍之后,我们就知道了为啥 线程池 的 对象我们不直接 new 了

 

这种方法就是 工厂模式 

不同的几种线程池 

第一种: 

此时构造出的线程池对象,有一个基本特点,线程数目是能够动态适应的。

cached: 缓存,用过之后不着急释放,先留着以备下次使用。

也就是说,随着往线程池里添加任务,这个线程池中的线程会根据需要自动被创建出来,创建出来之后也不会着急销毁,会在池子里保留一定的时间,以备随时再使用。

 

除了上边的线程池,我们还有其他的线程池:

第二种 :

这个方法就需要我们指定 创建几个线程,线程个数是固定的 (Fix:固定)

第三种:

只有单个线程的线程池: 

第四种 :

类似于 定时器, 只是 不是只有一个 扫描线程 负责执行任务了,而是有多个线程执行时间到的任务.

 第一种和第二种常用

上述这几个工厂方法生成的线程池,本质上都是对 一个类进行的封装 ——  ThreadPoolExector

ThreadPoolExector 这个类的功能十分丰富,它提供了很多参数,标准库中上述的几个工厂方法,其实就是给这个类填写了不同的参数来构造线程池。 

 ThreadPoolExector 的使用方式

ThreadPoolExector 的核心方法:

1.构造方法

2.注册任务(添加任务)

注册任务(简单):submit 

 

⁜⁜ 构造方法⁜⁜【经典面试题】

构造方法中的参数,很多,且重要, 

我们打开Java文档     Overview (Java Platform SE 8 ) (oracle.com)

打开这个包  juc —— 这个包里放的试和 “并发编程” 相关的内容(Java中,并发编程最主要的体现形式就是多线程)

 

点进这个包然后往下找: 

 

然后我们直接翻到构造方法 :

 

上面的四个构造方法,都差不多,就是参数个数 不一样,第四个 参数最多,能够涵盖上述的三个版本。 

所有我们重点看第四个构造方法: 

 

这一组参数,描述了线程池中,线程的数目: 

 

 

这个线程池里的线程 的数目试可以动态变化的,

变化的范围就是【corePoolSize, maximumPoolSize】

那 “核心线程”  和 “最大线程” 如何理解呢?

如果把一个线程池,理解为一个公司,此时,公司里有两类员工

        1.正式员工

        2.实习生

那正式员工的数目,就是核心线程数,正式员工 + 实习生的数目就是最大线程数

正式员工和实习生的区别:

正式员工,允许摸鱼,不会因为摸鱼被公司开除,有劳动法罩着。

但是实习生,不允许摸鱼,如果这段时间任务多了,此时,就可以多搞几个实习生去干活,如果过段时间任务少了,并且这样的状态还持续了一定时间,那空闲的实习生就可以裁掉了。

这样做,既可以满足效率的要求,又可以,避免过多的系统开销 。

ps: 

 使用线程池,需要设置线程的数目,数目设置多少合适?

 一定不是一个具体的数字!!!因为在接触到实际的项目代码之前,这个数目是无法确定的!!!

一个线程 执行的代码,主要有两类:

1.cpu 密集型:代码里主要的逻辑是在进行 算术运算/逻辑判断。

2.IO 密集型:代码里主要进行的是IO操作。

—— 假设一个线程的所有代码都是 cpu 密集型代码,这个时候,线程池的数量就不应该超过N,就算设置的比N大,此时也无法提高效率,因为cpu吃满了。

—— 假设一个线程的所有代码都是 IO 密集型代码,这个时候不吃cpu,此时设置的线程数,就可以是超过N,(一个核心可以通过调度的方式来并发执行)

上述,我们就知道了,代码不同,线程池的线程数目设置就不同,我们无法知道一个代码,具体多少内容是cpu密集,多少内容是IO密集。所以我们无法确定 数目设置多少合适。

正确做法:使用实验的方式,对程序进行性能测试,测试的过程中尝试修改不同的线程池的线程数目,看那种情况,更符合要求。

这一组参数,描述了允许实习生摸鱼的时间,(实习生不是 一摸鱼就马上被开除)

 

 

这个参数的意思是 阻塞队列 ,用来存放线程池里的任务。

 

可以根据需要,灵活设置这里的队列是啥,比如需要优先级, 就可以设置 PriorityBlockingQueue

如果不需要 优先级,并且任务数目是相对恒定的,可以使用 ArayyBlockingQueue,如果不需要优先级,并且任务数目变动比较大,就可以用 LinkedBlockingQueue

 

这个参数就是 工厂模式的体现 ,此处使用 ThreadFactory 作为 工厂类 由这个类负责创建线程

 

使用工厂类来创建线程,主要是为了在创建线程的过程中,对线程的属性做出一些设置。 

如果手动创建线程,就得手动设置这些属性,就比较麻烦,使用工厂方法封装一下,就更方便。 

 

下面这个参数是最重要的  ,是线程池的拒绝策略

 

一个线程池,能容纳的任务数量,有上限,当持续往线程池里添加任务的时候,一旦达到了上限,还继续添加,会出现什么效果?

拒绝策略就是来解决这个问题的: 不同的拒绝策略有不同的效果。

 

 上面的这四个就是不同的拒绝策略

如果队列满了,再添加就直接抛出异常 

 

新添加的任务,由添加任务的线程负责执行 

 

丢弃最老的任务 

 

丢弃当前新加的任务 

 

 实现一个简单的线程池

这个代码比较简单,就不多说了,代码里都有注释 

import java.awt.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @Author: iiiiiihuang*/
public class ThreadPool {//任务阻塞队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(4);//通过这个方法,把任务添加到队列中public void submit(Runnable runnable) throws InterruptedException {//此处的拒绝策略,相当于第五种策略,阻塞等待(下策)queue.put(runnable);}//构造方法public ThreadPool(int n) {//创建出n个线程,负责执行上诉队列中的任务for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {//让这个线程,从队列中消费任务,并执行try {//取出Runnable runnable = queue.take();//执行runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}}
}

 

 

 

关注,点赞,评论,收藏,支持一下╰(*°▽°*)╯╰(*°▽°*)╯

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

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

相关文章

pte初步认识学习

我们的时间的确很少&#xff0c;但是我们每天都乐意将珍贵的时间浪费在大量毫无意义的事情上 目录 pte介绍 PTE口语评分规则 pte架构 计算机科学23 QS排名 《芭比》 pte介绍 PTE口语评分规则 有抑扬顿挫 对于连读 不能回读 native pte对于个别单词没有读好&#xff0c…

【探索C语言中VS调试技巧】:提高效率和准确性

文章目录 前言1. 什么是bug&#xff1f;2. 调试是什么&#xff1f;有多重要&#xff1f;2.1 调试是什么&#xff1f;2.2 调试的基本步骤2.3 Debug和Release的介绍 3. Windows环境调试介绍3.1 调试环境的准备3.2 学会快捷键3.3 调试的时候查看程序当前信息3.3.1 查看临时变量的值…

C语言生成随机数、C++11按分布生成随机数学习

C语言生成随机数 如果只要产生随机数而不需要设定范围的话&#xff0c;只要用rand()就可以&#xff1b;rand()会返回一随机数值, 范围在0至RAND_MAX 间&#xff1b;RAND_MAX定义在stdlib.h, 其值为2147483647&#xff1b; 如果想要获取在一定范围内的数的话&#xff0c;直接做…

【数据分享】2023年全国地级市点位数据(免费获取\shp格式\excel格式)

地级市点位数据是我们各项研究中经常使用到的数据&#xff0c;在之前的文章中我们分享过2022年度的地级市及以上城市的点位数据&#xff08;可查看之前的文章获悉详情&#xff09;。本次我们带来的是2023年度的全国范围的地级市及以上城市的点位数据&#xff0c;点位位置为市政…

大数据Flink(八十四):SQL语法的DML:窗口聚合

文章目录 SQL语法的DML:窗口聚合 一、滚动窗口(TUMBLE)

[激光原理与应用-68]:如何消除50Hz工频干扰和差分信号应对工频干扰

目录 一、什么工频干扰 1.1 什么工频干扰 1.2 工频干扰的幅度 1.3 工频干扰如何进入设备 1.4 工频干扰的负面影响 二、如何消除工频干扰 2.1 要消除工频干扰&#xff0c;可以考虑以下方法&#xff1a; 2.2 要具体消除工频干扰&#xff0c;可以采取以下措施 2.3 使用差…

React(react18)中组件通信04——redux入门

React&#xff08;react18&#xff09;中组件通信04——redux入门 1. 前言1.1 React中组件通信的其他方式1.2 介绍redux1.2.1 参考官网1.2.2 redux原理图1.2.3 redux基础介绍1.2.3.1 action1.2.3.2 store1.2.3.3 reducer 1.3 安装redux 2. redux入门例子3. redux入门例子——优…

Mybatis 中 SQL 注入攻击的 3 种方式

SQL注入漏洞作为WEB安全的最常见的漏洞之一&#xff0c;在java中随着预编译与各种ORM框架的使用&#xff0c;注入问题也越来越少。往往对Java Web应用的多个框架组合而心生畏惧&#xff0c;不知如何下手&#xff0c;希望通过Mybatis框架使用不当导致的SQL注入问题为例&#xff…

[Qt/C/C++]JSON和程序发布

文章摘于 爱编程的大丙 文章目录 1. JSON1.1 Json数组1.2 Json对象1.3 注意事项 2. Qt中JSON操作2.1 QJsonValue2.2 QJsonObject2.3 QJsonArray2.4 QJsonDocument2.5 举例2.5.1 写文件2.5.2 读文件 3. cjson库的使用3.1 cJSON结构体3.2 cJson API3.2.1 数据的封装3.2.2 Json对…

Kotlin File FileTreeWalk walkTopDown onEnter onLeave

Kotlin File FileTreeWalk walkTopDown onEnter onLeave Python遍历文件目录os.walk_for subfolder in subfolders: print(foldername/_zhangphil的博客-CSDN博客import osfor folderName, subfolders, filenames in os.walk(rD:\code\vs_code): print(当前文件夹: folderName…

踩坑:Invalid character found in method name. HTTP method names must be tokens

一、原因 在进行本地小程序与服务端请求时,由于加了签名认证,访问接口时报错 Spring boot端 小程序端 二、解决方案 2.1 更改访问路径 将https:更换成http: 示例:https://localhost:8080 改为 http://localhost:8080 2.2其他原因 ssl证书到期了Tomcat的header缓冲区大小不…

Python 搭建编程环境

一、搭建编程环境 1、下载python 官网&#xff1a;https://www.python.org 2、开始安装 下载安装版本&#xff0c;双击下载的安装包&#xff0c;如下&#xff1a; 步骤一&#xff1a; 步骤二&#xff1a; 步骤三&#xff1a; 安装完成后执行下面的操作&#xff0c;判断是否…

用Jmeter进行压测详解

简介&#xff1a; 1.概述 一款工具&#xff0c;功能往往是很多的&#xff0c;细枝末节的地方也很多&#xff0c;实际的测试工作中&#xff0c;绝大多数场景会用到的也就是一些核心功能&#xff0c;根本不需要我们事无巨细的去掌握工具的所有功能。所以本文将用带价最小的方式讲…

vue的由来、vue教程和M-V-VM架构思想、vue的使用、nodejs

vue vue的由来 vue教程和M-V-VM架构思想 vue的初步简单使用 nodejs vue的由来 # 1 HTML(5)、CSS(3)、JavaScript(ES5、ES6、ES11)&#xff1a;编写一个个的页面 -> 给后端(PHP、Python、Go、Java) -> 后端嵌入模板语法 -> 后端渲染完数据 -> 返回数据给前端 ->…

国泰君安基本操作学习

对于主面板 1.放大或者缩小k线方法&#xff1a;按着ctrl键滚轮 2.切到历史k线&#xff1a;不断缩小k线后&#xff0c;把鼠标放置k线位置再按着ctrl滚轮放到可以切到当时的历史数据。 ★3.上下切换股票&#xff1a;滚轮。 ★4.面板直接输入股票代码后&#xff0c;自动弹出窗…

驱动开发练习,platform驱动模型的使用

一.总线模型介绍 linux中将一个挂载在总线上的驱动的驱动模型分为三部分&#xff1a;device、driver和bus&#xff1b; device部分&#xff1a;用来保存设备信息对象&#xff0c;在内核中一个klist_device链表中进行管理&#xff1b; driver部分&#xff1a;用来保存驱动信息对…

“毛细血管”的进化:华为分销业务如何让伙伴也有“高能级”

作者 | 曾响铃 文 | 响铃说 数字化蓬勃发展的大时代&#xff0c;除了那些中、大型企业&#xff0c;数量更为庞大的小微企业同样有借助数字化产品、服务来提升企业经营的需求&#xff0c;由此也带来了广袤的数字化分销市场。 这里处在聚光灯之外&#xff0c;很少被数字化时代…

PHP8的类与对象的基本操作之成员变量-PHP8知识详解

成员变量是指在类中定义的变量。在类中可以声明多个变量&#xff0c;所以对象中可以存在多个成员变量&#xff0c;每个变量将存储不同的对象属性信息。 例如以下定义&#xff1a; public class Goods { 关键字 $name; //类的成员变量 }成员属性必须使用关键词进行修饰&#xf…

无涯教程-JavaScript - SUMIFS函数

描述 SUMIFS函数添加其满足多个条件的所有参数。 语法 SUMIFS (sum_range, criteria_range1, criteria1, [criteria_range2, criteria2] ...)争论 Argument描述Required/OptionalSum_rangeThe range of cells to sum.RequiredCriteria_range1 使用Criteria1测试的范围。 Cr…

leetcode646. 最长数对链(java)

最长数对链 题目描述贪心解法二 动态规划 dp 题目描述 难度 - 中等 leetcode646. 最长数对链(java) 给你一个由 n 个数对组成的数对数组 pairs &#xff0c;其中 pairs[i] [lefti, righti] 且 lefti < righti 。 现在&#xff0c;我们定义一种 跟随 关系&#xff0c;当且仅…