线程池概念、线程池的不同创建方式、线程池的拒绝策略

文章目录

  • 💐线程池概念以及什么是工厂模式
  • 💐标准库中的线程池
  • 💐什么是工厂模式?
  • 💐ThreadPoolExecutor
  • 💐模拟实现线程池

💐线程池概念以及什么是工厂模式

线程的诞生是因为,频繁的创建进程太重量了(开销较大),所以引入了线程,但是呢,对于线程来讲,如果更加频繁的创建和销毁,那么开销也会慢慢的变大,所以,又引入了两种经典的方法来进一步提高:

1.协程:又称为轻量级线程,线程比较轻量是因为线程省略了分配资源的环节,而协程它在着基础上又省略了操作系统调度执行的环节,由程序员自己调度;在Java中呢,主要使用线程池,所以对于协程只是简单提一下;

2.线程池

举一个例子:

假如我是一个很漂亮的妹子,又有许多的男生正在追我,然后我就选择了一个男生A做我男朋友,但是呢,经过一段时间之后,我就腻了,就想要和男生B谈恋爱,所以,我就和男生A提出了分手,然后和男生B培养感情,等到有了感情基础,等有了感情基础后就拿下男生B,但是,过来一段时间后,我又想和男生C谈恋爱,所以就接着重复上面的套路,先培养感情等等………

而对于上面这种换男朋友的方式,感觉效率太慢,所以,我就有了一种新的方式,在和男生A谈恋爱的同时,偷偷的和男生B、C、D等多个男生培养感情,等到我向和谁谈恋爱时,那不就是捅破一层窗户纸的事情么,就可以挑选一个直接谈恋爱,这样的效率不久高了很多么,所以,对于偷偷的和我培养感情的这群男生也就可以称为“备胎池”;

而我们的线程池也是上面这种模式,在向池中添加任务时,直接从线程池中拿线程就可以了,就不比再创建了,直接拿过来使用即可,这样也就降低了线程创建的开销;所以线程池的就是先把线程创建好,放进池子里,等到后续想要使用时,直接从池子里取;

这里就会有一个问题:为啥从线程池里面取线程比创建线程效率高?

首先,创建新的线程这个动作,是内核态+用户态相互配合完成的;

而从线程池中取这个动作,是用户态操作完成的;

所以这里就涉及到了两个新名词,什么是用户态,什么是内核态

如果一段程序是在系统内核中完成的,此时就称为内核态

如果不是,则称为用户态;

而操作系统呢,是由内核+配套的应用程序组成的,创建线程,就需要调用系统API,进入到内核中,按照内核态的方式来完成一系列的动作;

但是,为什么内核态操作的效率比较低呢?请看下图

💐标准库中的线程池

在Java中,提供了一个类——Executors创建线程池;但是,线程池对象的创建并不是直接new出来的,而是通过一个方法的返回值,返回了一个线程池对象;

public class MyThreadPool {public static void main(String[] args) {//创建一个动态的线程池ExecutorService es = Executors.newCachedThreadPool();es.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}

创建线程池对象分为以下步骤:

1.使用Executors.newCachedThreadPool 创建出一个动态增长的线程池,为什么要用Executors.newCachedThreadPool的方式创建线程池,而不是直接new Executors?这里就涉及到了一个设计模式——工厂模式:

💐什么是工厂模式?

工厂模式:定义一个工厂类,通过调用工厂类中的不同方法来实现对象的实例化,从而创建出不同作用的对象;

举个例子,我们在创建对象时,会使用new关键字,通过构造方法来创建对象,但是使用构造方法创建对象会又很大的局限性,举个例子:假如我现在想要使用笛卡尔坐标来创建一个点对象,代码如下:

public class Point {//笛卡尔坐标系需要提供一个x,y坐标private int x;private int y;//通过笛卡尔坐标的方式创建一个对象public Point(int x, int y) {};
}

但是,我现在又想通过极坐标的方式创建点对象

public class Point {private int x;private int y;//通过笛卡尔坐标的方式创建一个对象public Point(int x, int y) {};//极坐标的方式就需要提供一个半径和角度public Point(int r, int a) {};
}

但是以上这种方式就会编译错误,因为,如果想要使用多种构造方法的方式创建对象的话,就需要将构造方法重载,而重载的条件是要保证参数列表的类型或者个数不同,所以以上代码是行不通过的,针对这种问题,就可以利用工厂模式解决

public class Point {private int x;private int y;public void setX(int x) {this.x = x;}public void setY(int y) {this.y = y;}
}//创建一个点对象的工厂
class PointFactor{//通过笛卡尔坐标系创建对象public static Point newPointByXY(int x, int y) {Point p = new Point();//对Point中的属性进行初始化p.setX(x);p.setY(y); return p;}//通过极坐标创建对象public static Point newPointByRA(int r, int a) {Point p = new Point();p.setX(r);p.setY(a);return p;}
}//测试类
class Main{public static void main(String[] args) {//这样通过调用点工厂中不同方法,就可以根据不同的方式创建出对象Point point1 = PointFactor.newPointByXY(5,2);Point point2 = PointFactor.newPointByRA(10,20);}
}

回到这里的线池:

public class MyThreadPool {public static void main(String[] args) {//创建一个动态的线程池ExecutorService es = Executors.newCachedThreadPool();es.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}

在线程池中,也提供了几个比较重要的方法:

(重点)创建线程池也提供了几种不同的方式:

  • newCachedThreadPool() 创建线程数目动态增长的线程池

    这种方式创建的线程池,池子中的线程会根据你添加的任务的需要,自动创建线程出来,线程结束以后也不会立即销毁,而是会在池子中保留一段时间,以备后续再随时使用;

  • newFixedThreadPoll() 创建固定线程数的线程池

  • newSingleThreadPool() 创建只包含一个线程的线程池

  • newScheduleThreadPool() 类似于定时器,只不过不是一个扫描线程,而是多个扫描线程执行时间到的任务

方法的返回值类型是ExecutorService

通过ExecutorService定义的对象调用submit()方法注册一个任务到线程池中

    public static void main(String[] args) {//创建一个动态的线程池ExecutorService es1 = Executors.newCachedThreadPool();ExecutorService es2 = Executors.newFixedThreadPool(5);ExecutorService es3 = Executors.newSingleThreadExecutor();ExecutorService es4 = Executors.newScheduledThreadPool(6);//指定扫描线程的数量}

上述几个使用工厂方法创建的线程池,本质上都是对一个类进行了封装,这个类就是——ThreadPoolExecutor

这个类的功能非常丰富,提供了很多不同参数方法,上述的几个工厂方法呢,其实就是给这个ThreadPoolExecutor 类填写了不同的构造参数从而创建出了不同的线程池;

接下来就看一下ThreadPoolExecutor的使用方法👇

💐ThreadPoolExecutor

ThreadPoolExecutor的构造方法中提供了很多可选的参数,进一步的细化了线程池的设定,下面针对这些参数进行一个讲解:

在这里插入图片描述

上图就是ThreadPoolExecutor的所有构造方法,也可以看到,最后一个构造方法的参数最多,并且当中的参数也都包含了其他三个方法的参数,所以,这里针对最后一个方法参数进行讲解:

  • int corePoolSize :核心线程数

  • int maximumPoolSize :最大线程数目

    在一个线程池中,是有多个线程的,以上两个参数就指定了线程池中线程数目的范围,最少有corePoolSize个线程,最多不会超过maxMumPoolSize个线程

  • long keepAliveTime 和 TimeUnit unit :空余线程存活的时间以及时间的单位

    在创建线程时, 默认会先使用核心线程数,上面提到过,当任务执行结束后,线程不会立马销毁,而是会有一个保留的时间,一方面是为了如果后续再需要使用时,就不用再进行创建,另一方面是,当保留时间到了以后,进行销毁,也减少了资源消耗,后续使用时再进行创建即可

  • BlockingQueue workQueue :阻塞队列

    当使用submit向线程池中添加任务时,如果任务个数少于核心线程数,那么会创建新的线程去执行任务,如果任务个数超过了核心线程数,就会先添加到阻塞队列中,然后工作线程从队列中取出任务执行,如果任务不能排队等候,那么也会创建一个新的线程,前提是不会超过最大的线程数,需要注意的是,这里的队列不光可以是阻塞队列,还可以是其他的队列,例如如果需要使用优先级就可以设置为:PriorityBlockingQueue,如果任务数目变动不大就可以使用:ArrayBlockingQueue,如果任务数目变动较大就可以使用:LinkedBlockingQueue;

  • ThreadFactory threadFactory :线程工厂类

    这个类也是工厂模式的体现,由ThreadFactory这个工厂类来创建线程,使用工厂类创建线程,主要是对线程的属性进行设置,通过这个类对这些属性进行了封装,就不需要我们手动进行设置;

  • ThreadPoolExecutor.DiscardPolicy :拒绝策略

    一个线程池中的线程数量是有上限的,当线程数量达到上限后,如果还继续往线程池中添加任务,那么针对不同的拒绝策略就会出现不同的效果;

    (重点)任务策略分为:

    在这里插入图片描述

这四种拒绝策略使用了类来实现,想要使用哪种策略,直接创建出对象,将对象传过去即可;

下面针对这四种策略进行一个讲解:

ThreadPoolExecutor.AbortPolicy :如果队列已经满了,直接抛出异常

ThreadPoolExecutor.CallerRunsPolicy :新添加的任务由调用任务的线程执行

ThreadPoolExecutor.DiscardOldestPolicy :丢弃任务队列中最老的任务

ThreadPoolExecutor.DiscardPolicy :丢弃新添7加的任务

上面讲过,针对于线程池可以设置线程的数目,但是,这个数目设置成多少合适呢?

针对这个问题,网上有很多的答案,假设CPU的逻辑核心数是N,线程数目的设置就有多个答案,例如:

N个,N+1个,N+2个,2N个等等;

针对以上答案,没有一个是正确的,因为这需要根据项目代码进行设置,一个线程执行的代码主要分为两类:

  • CPU 密集型

    CPU 密集型,代码里面的主要逻辑都是在算数运算/逻辑判断

  • IO 密集型

    IO 密集型,代码里面的主要逻辑都是在进行IO操作

    如果代码是都是CPU密集型,这时设置的线程数目就不能超过N,如果代码都是IO密集型的,此时设置的线程数目就可以超过N,而在现实中,没有代码都是纯CPU密集型和纯IO密集型的,同时,我们也无法知道有多少代码是CPU密集,有多少代码是IO密集的;

    所以要想知道该设置多少线程数,正确的做法就是用实验的方式,尝试改变线程池中不同线程的数目,来观察出哪种数目更合适;

💐模拟实现线程池

这里来模拟一个简单的newFixedThreadPool()版本的线程池,步骤:

  • 创建一个MyThreadPool,描述一个线程池

  • 使用一个阻塞队列组织所有的任务

    public class MyThreadPool {//创建一个阻塞队列组织所有的任务private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();public MyThreadPool(int n) {//创建n个线程for(int i = 0; i < n; i++) {Thread thread = new Thread(() -> {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {throw new RuntimeException(e);}});thread.start();}}public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for(int i = 0; i < 100; i++) {int n = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println(n);}});}}
    }
    

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

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

相关文章

【Godot4自学手册】第四十二节实现拖拽进行物品交换和数量叠加

这一节我们主要学习背包系统中的物品拖拽后&#xff0c;物品放到新的位置&#xff0c;或交换物品位置&#xff0c;如果两个物品属于同一物品则数量相加。具体效果如下&#xff1a; 一、修改item.tscn场景 给item.tscn场景的根节点Item添加Label子节点&#xff0c;命名为Numv…

[RPI4] 树莓派4b安装istoreos及使用 -- 1. 系统安装

最近在研究家庭智能化的一些东西,其中包括网络,智能家居等一系列内容,然后看过的资料有的想再回来看的时候就找不到了,然后就想着开这么一个系列,做一些记录,先从智能家居开始吧。 1 安装istoreos系统 iStoreOS 目标是提供一个人人会用的路由兼轻 NAS 系统,不管是作为路…

Java包介绍

今天看jdk文档&#xff0c;顺便写一下java几个包的作用。 java.applet 主要用于创建java applet小应用程序&#xff0c;可以嵌入到网页中能够呈现出特殊的效果&#xff0c;现在基本已经被废弃&#xff0c;很少使用。 java.awt AWT 是Abstract Window ToolKit (抽象窗口工具包…

我原以为政务类网站不追求漂亮,打脸啦,漂亮得颠覆你认知。

我原本以为政务类网站一定时沉稳、工整、信息量大的&#xff0c;这些和漂流都关联不上&#xff0c;直到最近看了一些网站&#xff0c;发现我的认识狭隘了。 政务类网站的设计风格通常需要注重以下几个方面&#xff1a; 稳重和专业感&#xff1a; 政务类网站需要给人以稳重、正…

perfect-scrollbar缩小浏览器窗口滚动条无线滚动的bug

https://github.com/mdbootstrap/perfect-scrollbar/issues/153

项目实践---Windows11中安装Zookeeper/Hadoop/Hive的部分问题解决

一.Hadoop与Hive兼容版本选择 正常来说&#xff0c;Hadoop与Hive版本不兼容会出现很多问题导致hive安装失败&#xff0c;可以先确定HIve的版本&#xff0c;比如&#xff1a;要用Hive3.1.2版本&#xff0c;该如何确定使用Hadoop的版本呢&#xff0c;需要我们在hive源码中找到对…

Python 类对象

Python 类对象 经典迭代器 可迭代对象的定义&#xff1a; 使用内置的iter可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法&#xff0c;那么对象就是可迭代的。序列都可以迭代。实现了__getitem__方法&#xff0c;而且接受从0开始的索引&#xff0c;这种对象也…

大型语言模型(LLM)和多模态大型语言模型(MLLM)的越狱攻击

随着大型语言模型&#xff08;LLMs&#xff09;的快速发展&#xff0c;它们在各种任务上表现出了卓越的性能&#xff0c;有效地遵循指令以满足多样化的用户需求。然而&#xff0c;随着这些模型遵循指令的能力不断提升&#xff0c;它们也越来越成为对抗性攻击的目标&#xff0c;…

vscode用vue框架1写一个登陆页面

目录 一、创建登录页面 二、构建好登陆页面的路由 三、编写登录页代码 1.添加基础结构 2.给登录页添加背景 3.解决填充不满问题 4.我们把背景的红颜色替换成背景图&#xff1a; 5.在页面中央添加一个卡片来显示登录页面 6.设置中间卡片页面的左侧 7.设置右侧的样式及…

记录react实现选择框一二级联动出现的问题

需求&#xff1a;用户在选择第一个选择框的选项后&#xff0c;第二个选择框的选项会根据第一个选择框的选择动态更新。如图所示 出现的问题 一级分类选择之后二级分类没有数据&#xff0c;第二次重新选择一级分类的时候&#xff0c;二级分类就会有值。 第一次点击截图&#x…

纯css星空动画

让大家实现一个这样的星空动画效果,大家会怎么做? js,不! 其实使用css就能写 我也不藏着掖着,源码直接放下面了 <script setup></script><template><div class"box"><div v-for"i in 5" :key"i" :class"layer…

文件上传漏洞-上篇

一、概述 文件上传漏洞可以说是日常渗透测试中用得最多的一个漏洞&#xff0c;用它获得服务器权限最快最直接。在web程序中&#xff0c;经常需要用到文件上传的功能。如用户或者管理员上传图片&#xff0c;或者其它文件。如果没有限制上传类型或者限制不严格被绕过&#xff0c…

Hadoop+Spark大数据技术(微课版)总复习

期末试卷组成 一、选择题(每小题2分&#xff0c;共20分) 二、判断题(每小题2分&#xff0c;共20分) 三、简答题(每小题5分&#xff0c;共20分) 四、程序分析题 (第1-5小题各6分&#xff0c;第6题10分&#xff0c;共40分) 图1 Hadoop开发环境 图2 HDFS 图3 MapReduce 图4 H…

Web 应用开源项目大全

Web 应用开源项目大全结合巴比达内网穿透实现WEB公开访问。 下面是一个Web应用的开源列表。没什么可说的&#xff0c;太疯狂了。尤其是Web 2.0那一堆。我不知道你怎么想&#xff0c;有些开源项目的源码写得挺不好的&#xff0c;尤其是性能方面。或许你会以为改一改他们就可以成…

【SCAU数据挖掘】数据挖掘期末总复习题库应用题及解析

1. 给定圆的半径为e &#xff0c;令 MinPts3&#xff0c;考虑下面两幅图。 &#xff08;1&#xff09;哪些对象是核心对象? m,p,o,r(因为这些核心对象在半径e的范围内都至少包含MinPts3个对象) &#xff08;2&#xff09;哪些对象是直接密度可达的? 对象q是…

智慧在线医疗在线诊疗APP患者端+医生端音视频诊疗并开处方

智慧在线医疗&#xff1a;音视频诊疗新纪元 &#x1f310; 智慧医疗新篇章 随着科技的飞速发展&#xff0c;智慧医疗正逐步走进我们的生活。特别是在线医疗&#xff0c;凭借其便捷、高效的特点&#xff0c;已成为许多患者的首选。而其中的“智慧在线医疗患者端医生端音视频诊疗…

使用Mixamo极简绑骨,导入unity中使用

如果你只想专注于角色建模&#xff0c;对于动画设计没有过多精力&#xff1b;如果你想白嫖别人的角色动画&#xff0c;用到自己的模型上&#xff1b;那么&#xff0c;这个网站很适合你&#xff1a;https://www.mixamo.com/ 操作步骤&#xff1a; 首先将自己的模型上传到这个网…

【kaggle数据集无法下载解决办法】

kaggle数据集无法下载的解决办法 当我们在做机器学习相关问题的时候&#xff0c;需要到kaggle网站上下载数据集&#xff0c;但是很多时候速度很慢或者连接超时等问题&#xff0c;此时解决办法如下&#xff1a; 在本地安装Kaggle API包 打开终端输入如下指令&#xff1a; pip i…

FL Studio 21.2.3官方中文版重磅发布,手把手教你图文安装

FL Studio 21.2.3官方中文版重磅发布纯正简体中文支持&#xff0c;更快捷的音频剪辑及素材管理器&#xff0c;多样主题随心换&#xff01; 在数字音乐制作领域&#xff0c;FL Studio一直以其强大的功能和用户友好的界面而备受赞誉。随着技术的不断进步和音乐制作需求的日益增长…

Linux根目录挂载点(/dev/mapper/centos-root)扩容

如果我们在安装系统是采用自定义分区的话&#xff0c;就可以提前规划好这个事情。但是如果平常没注意就直接采用默认安装的方式的话。一旦 根目录的容量耗尽&#xff0c;将会影响业务的运行。今天我们来扩容逻辑卷。 默认安装的话会给home目录分比较多的空间&#xff0c;我们可…