线程池概念的详解

news2024/11/16 21:56:09

  前言👀~

上一章我们介绍了什么是定时器以及如何去实现一个定时器,今天我们来讲解在多线程中同样很重要的一个内容线程池

线程池的出现

线程池概念

标准库中的线程池

工厂模式

newCacheThreadPool方法

newFixedThreadPool方法

ThreadPoolExecutor类

ThreadPoolExecutor的构造方法(重要)

线程数相关参数的解释

关于线程池线程数目设置(重要)

时间参数的解释

阻塞队列参数的解释

工厂类参数的解释

线程池的拒绝策略参数的解释(重要)

手动模拟实现线程池


如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞

12b46cd836b7495695ce3560ea45749c.jpeg

个人主页:N_0050-CSDN博客

相关专栏:java SE_N_0050的博客-CSDN博客  java数据结构_N_0050的博客-CSDN博客  java EE_N_0050的博客-CSDN博客


线程池的出现

我们知道线程诞生的意义,是因为进程的创建和销毁的操作开销太大并且效率不高,但是呢如果频繁的创建和销毁线程,开销也不小

下面使用两种办法,进一步提高线程效率

1.协程(轻量级线程)它比线程更轻,它把系统调度的过程省略,我们自己动手调度,当下流行的并发编程的手段,在java中不够流行,GO和Python使用的更多,java只能靠第三方库去使用协程,但是第三方库不一定靠谱

2.线程池:咋们使用它来提高效率,池这个词是计算机中一种比较重要的概念,很多地方涉及到,例如线程池、进程池、内存池、连接池


线程池概念

线程池:一种并发编程中常用的技术,用于管理和重用线程。它由线程池管理器(ThreadPoolExecutor)、工作队列(BlockingQueue)和线程池线程(Thread)组成。在我们使用一个线程的时候,提前把后面的线程创建好放在线程管理器中,这样接下来要使用这些线程的时候,直接从线程池管理器中取即可,并且从工作队列中取出任务,让线程池中的线程去执行任务。线程池最大的好处是为了避免频繁地创建和销毁线程的开销,以及控制并发执行的线程数量,从而提高系统的性能和资源利用率

简易解释:在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用

线程池可以看作是海王的鱼塘,即使有女朋友的情况下,还会看一个喜欢一个并且直接出手,全是它鱼塘里的鱼,即使分手了,从鱼塘里取条鱼直接衔接上,此时效率嘎嘎高,体验拉满


为什么从线程池中取线程比创建线程的效率高?

因为从线程池中取线程,这个操作属于是完全用户态的操作

创建线程这个操作是属于用户态+内核态配合完成的操作,就像我们之前写的代码,创建一个线程都是通过系统调用API去创建线程的,之前又说过操作系统中包含一个重要的模块内核,其实就是通过内核去进行这一系列的操作

例子说明为什么是用户态+内核态配合完成的操作:比如说我们要去办理营业执照,身份证肯定要带上的吧以及其他的证明,然后你去大厅找柜台的人给你办理营业执照,这时候人家说需要什么什么的,但是还差个身份证复印件,你没有,人家跟你说要么你去大厅那个复印机打印,要么我来帮你打印。你自己去打印的过程这个过程就是从线程池中取线程,这个操作属于是完全用户态的操作,因为是你自己去打印的,打印完立马就回来接着办理。你让人家给你打印这里会涉及到内核态,人家不知道去哪给你打印,也不知道这个期间它会不会干其他事,还要等。就像是操作系统是给所有进程提供服务的,当你要创建线程的时候,它会帮你去创建但是呢它可能还会去干一些其他的事情,不可控。所以可以这样想你自己去打印机复印就相当于直接从线程池取线程,去柜台找人复印就相当于内核帮你去创建线程。


标准库中的线程池

线程池对象因为是接口所有不能直接创建,需要通过一个专门的方法,返回一个线程池对象,后面有详细讲解

        ExecutorService service = Executors.newFixedThreadPool(3);

工厂模式

我们创建一个对象是通过new关键字去实现的,new这个关键字又会触发构造方法从而实现。但是构造方法存在一定的局限性,什么局限呢?我们要创建多个构造方法的时候,会受到重载的约束,参数类型要不同或者参数个数不同

这时候我们可以通过工厂模式解决这个问题单独搞一个类也就是工厂类,然后在这个类中搞些静态方法。通过一个static修饰的方法去代替构造方法完成初始化,只要把static修饰的方法名换一下,又可以代替一个不同的构造方法进行初始化,这样就不受重载的约束。由这样的静态方法负责构造出对象


newCacheThreadPool方法

这个newCacheThreadPool方法,看这英文就可以读出创建一个缓存线程池对象,缓存这玩意之前就说过,这里缓存的意思就是对于用过的线程我们不着急销毁,先保留一段时间留着下次用。然后创建出这个对象,有个特点能动态适应,什么意思呢?就是根据你的任务自动给你创建出线程,就比如说你的任务涉及到三个线程要工作,线程池对象会根据你的任务所需,自动创建出线程,并且像前面说的创建出来不着急销毁,先保留一段时间,以备后续再使用

newFixedThreadPool方法

创建出线程数量固定的线程池


 

还有很多能创建出线程池对象的方法根据需求选择使用对应的线程池


ThreadPoolExecutor类

上面的工厂方法生成的线程池本质上都是对一个ThreadPoolExecutor类进行封装
,标准库的几个工厂方法就是给这个类提供了不同参数的构造方法来创建线程池

ThreadPoolExecutor这个类的核心就两个,一个构造线程池,然后在里面有submit方法接收用户传入的任务然后进行处理

public class Test {
    public static void main(String[] args) {
        //ExecutorService这是一个接口不能实例化 用到工厂模式 通过Executors类的方法去创建线程池对象ThreadPoolExecutor这个类又包含线程池的重要属性
        //Executors这是一个类 这个方法能创建包含n个线程(不能说n是动态的)的线程池 返回值类型为 ExecutorService
        ExecutorService service = Executors.newCachedThreadPool();
        //通过 ExecutorService.submit 可以注册一个任务到线程池中
        service.submit(new Runnable() {
            //通过这个方法 把你要执行的任务添加到线程池中
            @Override
            public void run() {
                System.out.println("我是线程池对象");
            }
        });
    }
}

可以看到创建线程池对象返回的是一个ExecutorService类型,这是一个接口,下面我把源码展示给你们看,就知道为什么了

ThreadPoolExecutor的构造方法(重要)

ThreadPoolExecutor这个类的构造方法,最后一个构造方法涵盖所有的参数,我们围绕这个构造方法进行讲解,你也可以去官方文档看 https://docs.oracle.com/javase/8/docs/api/


线程数相关参数的解释

都是描述了线程的数目,corePoolSize是线程核心数目,maximmSize是线程最大数目,前面说了线程池里的线程数目是可以动态变化的,变化范围就是[线程核心数,线程最大数]

例子:可以把线程池理解为一个公司,我们都是公司的线程,但是有区别,正式员工是核心线程,有劳动法不会随便就被开除,实习生呢就是临时线程,随时都会被开除。正式员工的数目就是核心线程的数目,最大线程数目就是正式员工+实习生这样设置就能避免公司忙不过来,并且开销也减少了,专业点说就是满足效率的同时降低了系统开销

补充:核心线程(正式员工)不会消失,然后如果丢进去的任务认为超过了核心线程数,他就会创造临时线程数(实习生),然后临时的用完等待一会就消失了

关于线程池线程数目设置(重要)

有个问题我们在使用线程池,线程数目设置多少比较合适?只要你说了一个具体数字,就是错的,因为没接触过项目代码之前我们无法进行确认

设cpu核心数(逻辑核心数)是N

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

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

如果一个线程执行的代码都是cpu密集型,那线程池的线程数量设置等于N的时候已经是极限了,所以不超过N。因为超过N也没有多的核心数来处理这些线程了,此时通过操作系统的调度执行这些线程到cpu上工作反而会降低效率,增加开销

2.IO密集型:代码里主要进行的是IO操作,读写硬盘、网络通信等操作

如果一个线程执行的代码都是IO密集型,这个时候不吃CPU,此时设置线程池的线程数量可以超过N,因为上面说的cpu会调度执行这些线程也就是并发执行

实际项目代码中不可能全是执行一类的代码,而是两类都有,所以正确的做法是对程序进行测试,根据设置不同的线程数目来判断哪个线程数目下程序的综合性更好,或者根据要求来选择

时间参数的解释

keepAliveTime就是描述你作为实习生可以在公司摸鱼的时间,unit就是描述你摸鱼时间的单位(ms,s,min)。专业点说数值加单位组成时间。这两个参数什么意思呢?就是公司的任务少了持续了好一段时间为了节省开销就把你裁掉了,专业点说就是当线程数大于corePoolSize时,多余的临时线程能等待新任务的最长时间。只有当线程池中的线程数超过核心线程数时,这个参数才会生效

阻塞队列参数的解释

阻塞队列存放任务的也就是submit方法接收到的任务,存放到线程池中去进行处理,所以线程池中的任务是通过这个阻塞队列进行存储的。我们手动进行设置,也就是可以根据场景需求选择合适的队列,如果优先级优先采用PriorityBlockingQueue,如果任务数量比较固定采用ArrayBlockingQueue如果任务数量不固定变化大用LinkedBlockingQueue

工厂类参数的解释

就是使用这个工厂类去创建线程,能省去手动设置线程的一些属性(例如name、守护线程等),直接用工厂方法进行封装。省的你自己创建线程设置那些属性

线程池的拒绝策略参数的解释(重要)

首先要知道线程池中能存放的任务是有限的,如果超出这个数量,继续添加会出现什么效果,就是由这个拒绝策略决定的不同拒绝策略所展现的效果不一样,根据场景需求去选择对应的策略,下面有讲解,稍微会点英语就能知道什么意思了


ThreadPoolExecutor.AbortPolicy:这英文单词AbortPolicy的意思就是抛弃策略,超出能存放的任务数量,直接抛出异RejectedExecutionException

ThreadPoolExecutor.CallerRunsPolicy:这英文单词CallerRunsPolicy的意思就是调用者执行策略,超出能存放的任务数量,让添加新任务的这个线程执行

ThreadPoolExecutor.DiscardOldestPolicy:这英文单词DiscardOldestPolicy的意思就是丢掉最老策略,超出能存放的任务数量,丢掉队列中最老的任务

ThreadPoolExecutor.DiscardPolicy:这英文单词DiscardOldestPolicy的意思就是丢掉策略,超出能存放的任务数量,直接丢掉新添加的任务


手动模拟实现线程池

首先我们知道标准库线程池中有啥,有处理任务的线程,以及接收任务的submit方法,以及阻塞队列存放任务,下面是代码实现,简单模仿创建固定线程数目的线程池比较

class MyThreadPool {
    BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);//阻塞队列存储我们的任务

    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
            Thread thread = new Thread(() -> {
                try {
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        //这里采用的拒绝策略 阻塞等待
        queue.put(runnable);
    }

}

以上便是本章线程池的内容,内容不少好好消化,我们下一章再见💕

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

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

相关文章

中俄汽车产业链合作前景广阔,东方经济论坛助力双边合作与创新

随着中国汽车零部件企业的竞争力和创新能力不断增强&#xff0c;中国汽车及零部件行业在俄罗斯的市场份额和品牌影响力显著提升&#xff0c;中俄两国在汽车产业链上的合作展现出巨大的潜力和广阔的前景。2024年5月&#xff0c;俄罗斯乘用车新车销量达到12.8万辆&#xff0c;同比…

pwn 基础环境搭建

pwn 基础环境搭建 基础环境搭建 24.04个人感觉相比较22.04有很多改变 安装中文输入法 选择区域与语言 增加输入法 ok 安装zsh 通过oh-my-zsh管理zsh sudo apt install curl sudo apt install zsh sudo apt install git gcc g make build-essential wget gedit sh -c &qu…

<Linux> 多线程

文章目录 线程线程互斥锁死锁 线程同步生产者消费者模型POSIX信号量基于环形队列的生产消费模型 线程池 线程 线程是进程内部可以独立运行的最小单位 进程是资源分配的基本单位&#xff0c;线程是调度器调度的基本单位 线程在进程的地址空间内运行 进程内的大部分资源线程是…

C++ | Leetcode C++题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution { public:string shortestPalindrome(string s) {int n s.size();vector<int> fail(n, -1);for (int i 1; i < n; i) {int j fail[i - 1];while (j ! -1 && s[j 1] ! s[i]) {j fail[j];}if (s[j 1] …

SpringBoot 集成Swagger在线接口文档 接口注解

介绍 Swagger接口文档是一种自动生成、描述、调用和可视化的RESTful风格Web服务接口文档的工具。它通过一系列的规范和自动化工具&#xff0c;极大地简化了后端开发人员与前端开发人员之间的协作。 依赖 <!--swagger--> <dependency><groupId>io.springfo…

24年河南特岗教师招聘流程+报名流程

河南特岗教师报名流程如下 1.登录河南省特岗招聘网 登录河南省特岗招聘网注册账号和密码&#xff0c;账号可以是手机号或者身份证号&#xff0c;密码自己设置 2.注册登录账号 注册完账号重新登录账号&#xff0c;输入身份证号、手机号、密码、验证码 3.浏览考试须知 填写个人信…

基于惯性加权PSO优化的目标函数最小值求解matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于惯性加权PSO优化的目标函数最小值求解matlab仿真。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 &#xff08;完整程序运行后无水印&#xff09;…

机器学习原理之 -- 最近邻算法分类:由来及原理详解

最近邻算法&#xff08;k-Nearest Neighbors&#xff0c;k-NN&#xff09;是一种简单且直观的分类算法&#xff0c;广泛应用于分类和回归问题。由于其易于理解和实现&#xff0c;k-NN在数据挖掘、模式识别和机器学习领域中占据重要地位。本文将详细介绍最近邻算法的由来、基本原…

使用 bend-ingest-kafka 将数据流实时导入到 Databend

作者&#xff1a;韩山杰 Databend Cloud 研发工程师 https://github.com/hantmac Databend是一个开源、高性能、低成本易于扩展的新一代云数据仓库。bend-ingest-kafka 是一个专为 Databend 设计的实时数据导入工具&#xff0c;它允许用户从 Apache Kafka 直接将数据流导入到 D…

【UML用户指南】-27-对体系结构建模-制品

目录 1、组成结构 2、制品的种类 2.1、部署制品 &#xff08;deployment artifact&#xff09; 2.2、工作产品制品 &#xff08;work product artifact&#xff09; 2.3、执行制品 &#xff08;execution artifact&#xff09; 3、标准元素 4、常用建模技术 4.1、对可执…

CGLib动态代理技术

基于CGLib的动态代理机制&#xff0c;ProxyFactoryy无需再像JDK动态代理那样实现一个interface&#xff0c;实际情况下可能这个interface并不存在&#xff0c;只需要实现另外一个接口MethodInterceptor即可 package com.hmdp.service.尚硅谷的代理模式3; //CGlib代理import …

UE5 03-物体碰撞检测

在你需要碰撞的物体上添加一个碰撞检测组件 碰撞预设 设置为NoCollision,这样移动过程中就不会有物理碰撞阻挡效果,只负责检测是否碰撞,比较难解释,如果学过Unity的话,可以把它理解成 Collision 为 Trigger

INFINI Console 使用介绍

上次在《INFINI Easysearch尝鲜Hands on》中我们部署了两个节点的Easysearch&#xff0c;并且也设置了Console对集群进行监控。那么今天我们再来介绍下INFINI Console的使用。 INFINI Console 仪表盘功能介绍 INFINI Console 是一个功能强大的数据管理和分析平台&#xff0c;…

conda env pip install error:No space left on device

conda 环境 pip install error&#xff1a;No space left on device 文章目录 conda 环境 pip install error&#xff1a;No space left on device现象1 实验2 分析和解决办法 现象 非root用户的服务器&#xff0c;需要安装环境&#xff0c;安装的环境超过2GB sudo pip insta…

Roboflow自动标定数据集

最近需要自己打数据集&#xff0c;记录一下用Roboflow来打标签。 https://roboflow.com/&#xff08;官网&#xff09; 进入官网先注册&#xff0c;注册完成后进入这个界面。 我先讲如果不想让数据集公开怎么办&#xff0c;因为这里每个新建的都是公开的。新账号进去应该进去…

Python | Leetcode Python题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution:def shortestPalindrome(self, s: str) -> str:n len(s)fail [-1] * nfor i in range(1, n):j fail[i - 1]while j ! -1 and s[j 1] ! s[i]:j fail[j]if s[j 1] s[i]:fail[i] j 1best -1for i in range(n - 1,…

LIS2DH12

LIS2DH12 是属于“nano”系列的超低功耗高性能 3 轴线性加速度计&#xff0c;具有数字 I 2C、SPI 串行接口标准输出。 器件具有超低功耗工作模式&#xff0c;可实现高级节能、智能睡眠唤醒以及恢复睡眠功能。 LIS2DH12 具有2g/4g/8g/16g 的动态用户可选满量程&#xff0c;并能通…

Adobe Acrobat添加时间戳服务器

文章目录 前言一、Adobe Acrobat添加时间戳服务器1.打开Adobe Acrobat软件2.点击【菜单】→ 【首选项】3.点击【安全性】→【更多】4.点击【新建】5.输入【名称】→【服务器URL】 前言 一、Adobe Acrobat添加时间戳服务器 1.打开Adobe Acrobat软件 2.点击【菜单】→ 【首选项…

汽车电子零部件(15):车载TFT LCD显示模组

前言: 车载显示越来越受到重视,屏的使用越来越大且多,车载显示屏模组技术也在快速发展。 在复杂的显示技术世界中,薄膜晶体管(TFT,Thin Film Transistor)液晶显示器(LCD,Liquid Crystal Display)模块的制造证明了现代工程的奇迹。 TFT显示器是全彩色LCD,提供明亮、生…

WPF 3D绘图 点云 系列五

基本概念:点云是某个坐标系下的点的数据集。 可能包含丰富的信息,包括三维坐标X,Y,Z、颜色、分类值、强度值、时间等等 点云可以将现实世界原子化,通过高精度的点云数据可以还原现实世界。万物皆点云。 通过三维激光扫描仪进行数据采集获取点云数据,其次通过二维影像进行…