JavaEE初阶——多线程(六)——线程池

在这里插入图片描述

T04BF

👋专栏: 算法|JAVA|MySQL|C语言

🫵 小比特 大梦想

此篇文章与大家分享多线程的第六篇文章,关于线程池
如果有不足的或者错误的请您指出!

目录

    • 3.线程池
      • 3.1标准库的线程池
      • 3.2 标准库自己提供的几个工厂类
      • 3.3自己实现一个线程池
        • 完成大体框架
        • 接下来完善构造方法
        • 但是此时创建的线程相当于是"核心线程",我们还要设计在任务数量过多,线程忙不过来的时候,就要创建新的线程来缓解

3.线程池

池这个概念在计算机里面是很常见的概念.如常量池,线程池,进程池,内存池…
而普遍引入池的意义就是为了提高效率

我们在之前讲过,为了处理高并发场景,我们引入了线程池.是因为线程相对于进程在 创建和销毁 的时更占优势
但是随着时代的发展,对"频繁"这一概念又有了新的定义,越来越频繁了
在这种环境下,即使是线程创建和销毁的开销也不敢忽略,变得很明显了
那么我们就要进行优化:有两种优化方法
(1)线程池
(2)协程(也叫纤程)

注意:纤程是高版本java才引入的"虚拟线程" (本文不讲)

为什么引入多线程 / 纤程后就能够提高效率?
本质上就是因为 直接调用系统API去创建和销毁线程,需要用户态+内核 配合完成的工作
而通过 线程池 / 纤程 ,只需要通过用户态即可,不需要内核的配合

如果我们直接调用API,创建线程和销毁线程,这个过程需要内核来完成,而内核完成的工作很多时候是不可控的,会带来很大的开销
而使用线程池,就是提前将线程创建好了,放进用户态方面的数据结构里,后面要用的时候,直接从池子里面拿即可,用完了再放回到池子里面去,这个过程就完全是用户态代码,就不需要与内核进行交互了

3.1标准库的线程池

标准库提供的线程池是ThreadPoolExecutor
使用起来比较复杂,因为构造方法中包含了很多参数
在这里插入图片描述

其中第四个参数数量是最多的,我们理解完最后一个,就都能够理解了
(1)corePoolSize 和 maxmumPoolSize
第一个是核心线程库,第二个是最大线程数
标准库是这样设定的,将现线程分为核心线程(正式工)和非核心线程(临时工)
而maxmumPoolSize就是 核心线程数量 + 非核心线程数量
一个线程池被创建出来的时候,所包含的线程数量就有 核心线程数量那么多,此时线程池会提供一个submit方法,就是往线程池里面添加任务,一旦任务多了,已有的线程处理不过来(有很多任务在排队),那么此时就会自动创建额外线程来支撑更多的任务
但是即使创建了新的线程,总线程数量还是不能大于最大线程数量
过了一段时间后,任务没那么重了,线程相对清闲,此时就会将创建出来的非核心线程回收
而即使是回收,也只回收掉非核心线程,至少还是要保证,线程池里面的线程数量,不少于核心线程数
这样就能保证,在任务多的时候保证效率,在任务少的时候,系统开销小

在实际开发中,线程数量设置为多少合适?
实际上我们可能听说最多的是 根据计算机的cpu核心数来具体配置
但是与之相关的还有一个更为重要的,就是和你写的程序的时机特点有关
极端一点就可以分成两类:
(1)cpu密集型
在程序中涉及大量的cpu操作,比如
while(true) { count++; }
这种,代码逻辑都是在进行算术运算,逻辑判断,条件运算,函数调用等等
这种程序一跑,就能够立即吃满一个cpu核心
在这种情况下,你的最大线程数量是不能超过cpu核心数的
(2)IO密集型
在程序中涉及大量的等待IO操作(等待过程是不参与调度的)
IO密集型不只是标准输入输出,sleep,操作硬盘,操作网络…也属于IO密集型
那么此时最大线程数的瓶颈就不在与cpu上了,而是考虑其他方面
比如是网络操作程序,那么就要考虑网络带宽的瓶颈
例如你的网卡是 1Gbps,一个线程的读写速率是100Mbps,那么最多也就只能搞10个这样的线程了,硬盘也是同理,而至于标准输入输出这种,搞百八十个也没问题

但是实际上上述两种模型都太极端了,现实我们的程序应该是处于二者之间的,即逻辑中既包含cpu操作,又包含IO操作
最终的答案就是通过实验操作,找到一个合适的值
即对程序进行性能测试,测试的过程中设定不同的线程数量,最终根据实际程序的响应速度和系统开销,找到一个最合适的数目

(2)keepAliveTime,TimeUnit uiit
表示非核心线程,允许空闲的最大时间

非核心线程在线程池不忙的时候,不是立即回收的,通过我们设定的值,比如3s,那么在3s内,如果非核心线程没有任务执行了,此时就可以立即回收了

后面的unit实际上就是等待时间的单位,他是一个枚举类型
在这里插入图片描述

(3)workQueue
是线程池里面的任务队列
线程池会提供一个submit方法,让其他线程将任务提交到线程池里面,那么线程池里面就要有一个任务队列,把要执行的任务组织起来,实际上在线程池里面,线程消耗的任务就是任务队列里面的任务,来完成具体工作
具体来说就是其他线程会submit任务到这个队列中,这个队列里面存的元素就是一个Runnable对象,要执行的逻辑就是run方法里面的内容
而我们通过实际场景,就可以指定一个capacity的队列进去,也可以指定带有优先级的队列

(4)工厂模式
也是一种设计模式
主要是解决,基于构造方法创建对象太坑了的问题
举个例子:假设我们要描述一个点
在这里插入图片描述

但是我们可能要通过极坐标来表示一个点
在这里插入图片描述

但是构不成重载就会编译报错
此时就无法通过构造方法来表示不同构造点的方式了
而工厂模式的核心思路就在于不再使用构造方法创建对象,而是相当于给构造方法包装一层
在这里插入图片描述

这样的方法就叫做工厂方法 ,这种写法套路就是工厂模式

甚至,工厂方法被单独的类封装,此时这个类就是工厂类
而ThreadFactory就是标准库中用来创建线程的工厂类
这个工厂类主要就是用来批量给要创建的线程设置一些属性啥的,在工厂方法中就已经把线程的属性设置好了
平时我们一般也不会直接用这个,主要是搭配线程池来使用

(5)RejectedExcutionHandle handler
指的是拒绝策略,实际是是一个枚举类型
用于决定,如果当前任务队列满了,仍然要添加任务进来,要进行怎么样的处理
在这里插入图片描述
第一种是abortPolicy,表示直接抛出异常,让程序员快速知道,任务处理不过来了,代码罢工了
第二种是callerPolicy,表示该任务不是由线程池负责执行了,而是交给负责submit的对象去执行
第三种是discardOldestPolicy,表示删除掉原来任务列表里面最开始的任务,让新来的任务来队列中排队
第四种是discardPolicy,表示直接拒绝新来的任务.还是按照原来到节奏执行

3.2 标准库自己提供的几个工厂类

由于标准库自己也知道,ThreadPoolExecuter使用起来比较费劲,于是自己提供了几个工厂类,对于上述线程池又进行进一步封装了
在这里插入图片描述
上面这些就是标准库提供的创建线程池的工厂方法

    public static void main(String[] args) {
        //创建一个最普通的线程池,能够根据任务的数目,自动进行线程扩容
        Executors.newCachedThreadPool();
        //创建固定线程数目的线程池
        Executors.newFixedThreadPool(4);
        //创建一个只包含单线程的线程池
        Executors.newSingleThreadExecutor();
        //创建一个固定线程个数,但是任务延时执行的线程池
        Executors.newScheduledThreadPool(4);
    }

此时我们就可以通过线程池提供的submit方法往线程池里面添加任务了
在这里插入图片描述

如果我们限制好线程池里面线程的数量,就能更好发现线程的复用
在这里插入图片描述
注意此时由于线程池内部的线程是前台线程,因此进程不会结束

3.3自己实现一个线程池

一个线程池里面要包含哪些东西??
(1)有若干个线程
(2)有任务队列
(3)提供submit方法

完成大体框架
public class MyThreadPool {
    //任务队列
    private BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(100);
    public MyThreadPool(int nThread) {
        //这里初始化线程池
    }

    public void submit (Runnable runnable) throws InterruptedException {
        //提交任务队列
        this.blockingQueue.put(runnable);
    }
}

接下来完善构造方法
public class MyThreadPool {
    //任务队列
    private BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(100);
    public MyThreadPool(int nThread) {
        //这里初始化线程池
        for(int i = 0; i < nThread; i++) {
            Thread t = new Thread(() -> {
               while(true) {
                   try {
                       blockingQueue.take().run();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            });
            t.start();
        }
    }

    public void submit (Runnable runnable) throws InterruptedException {
        //提交任务队列
        this.blockingQueue.put(runnable);
    }
}

但是此时创建的线程相当于是"核心线程",我们还要设计在任务数量过多,线程忙不过来的时候,就要创建新的线程来缓解
public class MyThreadPool {
    //任务队列
    private BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(100);
    private int maxPoolSize = 0;

    private List<Thread> threadList = new ArrayList<>();
    public MyThreadPool(int corePoolSize,int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
        //这里初始化线程池
        for (int i = 0; i < corePoolSize; i++) {
            Thread t = new Thread(() -> {
               while(true) {
                   try {
                       blockingQueue.take().run();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            });
            threadList.add(t);
            t.start();
        }
    }

    public void submit (Runnable runnable) throws InterruptedException {
        //提交任务队列
        this.blockingQueue.put(runnable);
        //判断当前任务队列里面的任务是否比较多
        //如果是,那么就创建新的线程
        if(blockingQueue.size() >= 600 && threadList.size() <= maxPoolSize) {
            Thread t = new Thread(() -> {
               while(true) {
                   try {
                       blockingQueue.take().run();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }
            });
            t.start();
            threadList.add(t);
        }
    }
}

比较麻烦的是要回收非核心线程,就需要引入更多的数据结构了,同时也需要引入"定时器"(后面讲)

关于拒绝策略

实现拒绝策略的核心就在于submit这里,

(1)构造方法里面提供参数,用参数来表示哪种拒绝策略,使用成员变量记录一下

(2)submit的时候,先要判断当前任务队列里面的元素是否已经满了(自己设定一个阈值)

(3)如果确实比较多了,就根据刚刚构造时指定的拒绝策略,执行不同的逻辑

a)直接抛出异常

b)直接在submit里面调用Runnable方法

c)删除队列的元素(可以一次删多个)

d)丢弃当前元素,不添加到队列里面

感谢您的访问!!期待您的关注!!!

在这里插入图片描述

T04BF

🫵 小比特 大梦想

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决DataGrip连接MySQL8时出现时区错误问题

解决办法&#xff1a;在url后面拼接时区参数 ?serverTimezoneAsia/Shanghai

生成式AI在B端产品的应用分析

AI产品发展到现在&#xff0c;消费端的产品应用还受到比较大的限制&#xff1b;但是在B端&#xff0c;已经有了不错的表现。作者总结了AI产品在B端的几款应用&#xff0c;一起来看看表现如何。 生成式AI在B端产品的应用分析© 由 ZAKER 提供 随着今年生成式AI应用的大范围…

绝地求生:16款战术手套,你最钟爱哪一款?

大家好&#xff0c;我是闲游盒&#xff01; 喜迎PUBG七周年生日同时游戏里又迎来了一款新的战术手套&#xff0c;那么就让我们来回顾一下目前出游戏中的16款战术手套吧&#xff0c;看看你最中意的是哪一款&#xff1f; 1、MAZARIN1K 战术手套 2、SPAJKK 战术手套 3、SWAGGER 战…

机器学习(四)之无监督学习

前言&#xff1a; 前面写了监督学习的几种算法&#xff0c;下面就开始无监督啦&#xff01; 如果文章有错误之处&#xff0c;小伙伴尽情在评论区指出来&#xff08;嘿嘿&#xff09;&#xff0c;看到就会回复的。 1.聚类&#xff08;Clustering&#xff09; 1.1 概述&#xff…

8.4.3 使用3:配置单臂路由实现VLAN间路由

1、实验目的 通过本实验可以掌握&#xff1a; 路由器以太网接口上的子接口配置和调试方法。单臂路由实现 VLAN间路由的配置和调试方法。 2、实验拓扑 实验拓扑如下图所示。 3、实验步骤 &#xff08;1&#xff09;配置交换机S1 S1(config)#vlan 2 S1(config-vlan)#exit S…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套 (共9套&#xff0c;有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09; 部分题目分享&#xff0c;完整版获取&#xff08;WX:didadidadidida313&#xff0c;加我备注&#x…

LayuiMini使用时候初始化模板修改(下载源码)

忘记加了 下载 地址 &#xff1a; layui-mini: layuimini&#xff0c;后台admin前端模板&#xff0c;基于 layui 编写的最简洁、易用的后台框架模板。只需提供一个接口就直接初始化整个框架&#xff0c;无需复杂操作。 LayuiMini使用时候初始化模板官网给的是&#xff1a; layu…

立即刷新导致请求的response没有来得及加载造成的this request has no response data available

1、前端递归调用后端接口 const startProgress () > {timer.value setInterval(() > {if (progress.value < 100) {time.value--;progress.value Math.ceil(100 / wait_time.value);} else {clearInterval(timer.value);progress.value 0;timer.value null;time.…

电磁兼容(EMC):静电放电(ESD)抗扰度试验深度解读(六)

目录 1. 静电测试干扰方式 2. 案例一 3. 案例二 4. 案例三 5. 案例四 6. 总结 静电放电测试的复杂性决定了这项测试对产品的主要影响方式也是多样的。标准里介绍了几种常见的影响方式&#xff1a; 1. 静电测试干扰方式 在静电放电试验中&#xff0c;测试了受试设备对于…

CDN、边缘计算与云计算:构建现代网络的核心技术

在数字化时代&#xff0c;数据的快速传输和处理是保持竞争力的关键。内容分发网络&#xff08;CDN&#xff09;、边缘计算和云计算共同构成了现代互联网基础架构的核心&#xff0c;使内容快速、安全地到达用户手中。本文将探讨这三种技术的功能、相互关系以及未来的发展趋势。 …

大语言模型微调过程中的 RLHF 和 RLAIF 有什么区别?

目前想要深入挖掘大型语言模型&#xff08;LLM&#xff09;的全部潜力需要模型与我们人类的目标和偏好保持一致。从而出现了两种方法&#xff1a;来自人类反馈的人力强化学习&#xff08;RLHF&#xff09;和来自人工智能反馈的人工智能驱动的强化学习&#xff08;RLAIF&#xf…

rosdep一键修复

External Player - 哔哩哔哩嵌入式外链播放器 rosdep失败原因 通常在执行rosdep init操作时就会报错&#xff0c;问题的核心在于rosdep会访问raw.githubusercontent.com这个网址下的资源&#xff0c;例如https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/sour…

免费开源线上社交交友婚恋系统平台 可打包小程序 支持二开 源码交付!

婚姻是人类社会中最重要的关系之一&#xff0c;它对个人和家庭都有着深远的影响。然而&#xff0c;在现代社会的快节奏生活中&#xff0c;找到真爱变得越来越困难。在这个时候&#xff0c;婚恋产品应运而生&#xff0c;为人们提供了寻找真爱的新途径。 1.拓宽人际交流圈子 现代…

【Camera KMD ISP SubSystem笔记】CRM V4L2驱动模型

1. CRM为主设备 /dev/video0&#xff0c;先创建 v4l2_device 设备&#xff0c;再创建 video_device 设备&#xff0c;最后创建 media_device 设备/dev/media0 v4l2_device的mdev指向media_device&#xff0c;v4l2_device的entity链接到media_device的entities上&#xff08…

P1106 删数问题

本题为洛谷&#xff1a; #include<iostream> #include<string> using namespace std; int main(){string n;int k;cin>>n>>k;while(k--){for(int i0;i<n.length();i){if(n[i]>n[i1]){n.erase(i,1); break;} }for(int i0;i<n.length()-1&&…

SpringBoot学习之Kafka发送消费消息入门实例(三十五)

使用Kafka之前需要先启动fKafka,如何下载安装启动kafka请先参考本篇文章的前两篇: 《SpringBoot学习之Kafka下载安装和启动【Windows版本】(三十四)》 《SpringBoot学习之Kafka下载安装和启动【Mac版本】(三十三)》 一、POM依赖 1、加入kafka依赖 2、我的整个POM代码…

Jammy@Jetson Orin - Tensorflow Keras Get Started: 000 setup for tutorial

JammyJetson Orin - Tensorflow & Keras Get Started: 000 setup for tutorial 1. 源由2. 搭建环境2.1 安装IDE环境2.2 安装numpy2.3 安装keras2.4 安装JAX2.5 安装tensorflow2.6 安装PyTorch2.7 安装nbdiff 3. 测试DEMO3.1 numpy版本兼容问题3.2 karas API - model.compil…

Day 20 Linux的WEB服务——apache

WEB服务简介 目前主流的web服务器软件 Linux&#xff1a;apache &#xff0c; nginx Windows-server&#xff1a;IIS 服务器安装nginx或apache后&#xff0c;叫做web服务器&#xff08;又称WWW服务器&#xff09; web服务器软件属于C/S框架模型 web服务器是一种被动程序只…

单片机学习过程

继电器光耦隔离电压转换步进电机直流电机 arduino是最好用的一种&#xff0c;他提供了完整的设备库文件&#xff0c;任何外部设备只要查找相应的库&#xff0c;就可以很方便的使用 &#xff0c; 但是如果不去学习51 或stm32 或 嵌入式玩玩还可以&#xff0c;如果碰到没有实现的…

文字转粤语语音怎么转?文字转语音

文字转粤语语音怎么转&#xff1f;文字转粤语语音的应用&#xff0c;不仅展现了现代科技的魅力&#xff0c;也为我们提供了更加便捷的交流方式。它们将文字转化为粤语发音&#xff0c;让我们能够更直观地感受粤语的韵味和魅力。同时&#xff0c;这些软件还具备高度的可定制性&a…
最新文章