【Java EE初阶九】多线程案例(线程池)

news2025/1/16 16:02:16

一、线程池的引入

        引入池---->主要是为了提高效率;

        最开始,进程可以解决并发编程的问题,但是代价有点大了,于是引入了 “轻量级进程” ---->线程

        线程也能解决并发编程的问题,而且线程的开销比进程要小的多,但是如果线程太多,创建销毁线程的频率也会进一步提高,故此线程创建销毁的开销就不能忽视了。

        为了解决上述问题,大佬们给出了两个解决方案:

      1、引入轻量级线程---->也称为纤程/协程(节省了系统调度的开销)

        协程的本质是程序员在用户态代码中进行调度,不是靠内核的调度器调度的—>节省了许多的调度上的开销协程是在用户代码中,基于线程封装出来的,可能是N个协程对应1个线程,也可能是N个协程对应M个线程。

        2、引入 “线程池”

      线程池:把我们要使用的线程池提前创建好,这个线程执行完也不要直接释放而是存放到线程池中以备下次继续使用需要用这个线程的时候,再从线程池中拿,不需要的时候,就放在线程池中,并不会销毁它,这样就节省了创建/销毁线程的开销;

        在这个使用的过程中,并没有真正的频繁创建销毁,而只是从线程池里面取线程使用,等使用完了在还给线程池;

        为啥从线程池中取线程 比从系统中申请线程的创建更高效呢?

        下面讲解一下关于用户态和内核态的说明;

        假设在银行场景中,smallye要去这个银行办理一个业务,一般银行中大堂有复印机;这时,smallye没有带身份证复印件,此时smallye要去搞到身份证复印件,有两个选择:

        其一选择:把身份证给柜员,让柜员帮smallye复印,但是这个操作是不可控的,可能这个柜员中途被老板安排了其他活,那这个时候,就不能帮smallye复印身份证了,要等忙完老板安排的活,再帮smallye复印身份证;

        其二选择:smallye自己去大堂中复印身份证,这样就比较可控了,smallye可以很快的去到打印机,立马复印出来,再去办理他的业务。如下图所示:

        上述例子中大堂就是用户态,柜台就是内核态;

        从线程池中取线程,是纯用户态代码(可控)                                                                           通过系统申请创建线程,需要内核完成(不可控有风险);

2. 线程池的简单介绍

2.1 ThreadPoolExecutor类

        在java标准库中,ThreadPoolExecutor类表示线程池,ThreadPoolExecutor类是参数最多的构造方法,如下图所示:

        下面来详细讲解该构造方法里面的参数的具体含义:

         1、核心线程数和最大线程数(int corePoolSize,int maximumPoolSize):

        corePoolSize:核心线程数:(正式员工线程)

        maximumPoolSize:最大线程数:(正式员工线程 + 实习员工线程)

        eg:核心线程就是相当于公司里面的正式员工,同时最大线程数里面包含最大线程数和实习员工线程,对于实习员工线程来说就是就是可有可无的,当核心线程全部处于工作状态且还有大量的任务需要新的线程处理的时候,我们就会创建实习员工线程,来帮核心线程处理这些任务;当任务数量较少的时候,核心线程可以闲着,但是实习员工线程全部需要销毁;

        2、保持存活时间和存活时间的单位(long KeepAliveTime,TimeUnit unit)

        KeepAliveTime:保持存活时间:(实习生线程允许摸鱼的最大时间)

        unit:存活时间的单位:可以是hour 、 min 、 s 、 ms

        3、放任务的队列   (BlockkingQueue<Runnable> workQueue:)

        和定时器类似,线程池中也可以持有多个任务,要执行的任务,使用Runnable来描述任务的主体。

        4、线程工厂(ThreadFactory,threadFactory)

        通过这个工厂类创建线程对象(Thread对象),工厂类里面有方法封装了new Thread的操作,同时给Thread设置了一些属性,我们想要创建线程的时候可以直接使用工厂类的方法创建。

        eg:描述一个点,通过数学知识可以用二维坐标和极坐标来表示:二维坐标:(x,y) 极坐标:(r,α);故此我们通过new一个类来得到一个点,这个类里有两个构造方法,参数分别是(double x,double y),(double r,double α),那么这两个构造方法的参数类型都一样,构成不了重载,如下图所示:

        以上显示出我们想要给java类提供更多的构造方法,但是受到重载的影响限制,为了解决上述问题,我们引入了“工厂模式”,做一下修改:

        我们使用static修饰,更改方法名,通过不同的方法名获取类,在方法里new一个类,里面设置一些参数,再返回这个类,如下图所示:

        这样的类,就称为工厂类,工厂类里面得到类的方法就称为工厂方法。

        总的来说,通过静态方法来封装new操作,在这个静态方法设置不同的属性,构造对象的过程,就称为工厂模式。

        5、拒绝策略(RejectExecutionHandler handler)

        该参数是上述部分参数中最重要的一个;

        在线程池中有一个阻塞队列,且该队列容纳线程数量有限,如果这个任务队列满了,这时有往线程池中添加任务,这时候线程池要学会拒绝,由拒绝策略,在java标准库中就提供了以下四种拒绝策略,如下图所示:

        拒绝策略讲解:

        第一个策略:会直接抛出一个异常,这样,旧的任务执行不了,新的任务也执行不了

        第二个策略:把新的任务丢给添加任务队列的线程执行,不给入队列,同时旧的任务依然在执行

        第三个策略:把最旧的任务丢弃,添加最新的任务进来

        第四个策略:直接把新的任务丢弃了,不执行新的任务,旧的任务会继续执行

2.2 Executors类 

        ThreadPoolExecutor类本身使用起来比较复杂,java标准库给我们提供了另一个版本:把ThreadPoolExecutor封装了一下,这个类就是Executor工厂类,通过这个类创建出不同的线程池对象,在其内部,已经把ThreadPoolExecutor创建好了,并且设置了一些参数。

        Executor的简单使用,其中主要方法有一下4个,如图:

        eg:我们使用newFixedThreadPool(4)方法创建4固定个线程数目的线程池,再往里添加任务:

package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadDemo32 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("smallye");
            }
        });
    }
}

        结果如下:

        至于如何确定使用Executor或ThreadPoolExecutor,主要是看具体的情况;

2.3 线程池的执行流程

        主要有以下四个情况:
        1、当有任务要让线程池里面的线程执行时,会比较工作线程数和核心线程数,                     如果工作线程数 < 核心线程数,则会直接安排线程去执行这个任务。

        2、当工作线程数 > 核心线程数,即线程池中的核心线程数满了,会添加进阻塞任务队列中,添加任务队列前也会判断任务队列是不是空,是空就阻塞等待。

        3、如果线程池中的存活线程数 == 核心线程数,并且阻塞任务队列也满了,此时会判断是否到了最大线程数:maximumPoolSize,如果没有到达,就会让非核心线程去执行这个任务。

        4、如果当前线程数到达了最大线程数,则会执行拒绝策略

2.4 关于线程池中创建多少线程

        这是我们就需要关注该进程是cpu密集型还是io密集型;
        假设一个进程中,所有线程都是cpu密集型,这时每个线程的工作都是在cpu上执行的,此时,线程池中的数目就不应该超过N(cpu的逻辑核心线程数)

        假设一个进程中,所有线程都是IO密集型的,这时每个线程的大部分工作都是在等待IO,此时,线程池中的数目就可以远远超过N(cpu的逻辑核心线程数)

        实际上一个进程中的线程,有cpu密集型的,也有IO密集型的,只是比例不同。由于程序的复杂性,很难直接对线程池进行预估,更准确的做法是通过实验 / 测试的方法,找出合适的线程数目;

3. 线程池的模拟实现

        我们写代码实现一个简单的线程池:(直接写一个固定线程数目的线程池-->暂时不考虑线程的增加和减少),其中具体思路主要一下步骤:

  1. 提供构造方法,指定创建多少个线程池
  2. 在构造方法中,把这些线程都创建好
  3. 有一个阻塞队列,能够持有要执行的任务
  4. 提供submit方法,可以添加新的执行任务;

3.1 阻塞队列--->存放要执行的任务

// 就是一个用来保存任务的队列.
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

3.2 submit方法--->添加任务的方法,任务添加到队列中

  public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

3.3 构造方法--->指定创建多少个线程,线程在这个构造方法中都创建好了

// 通过 n 指定创建多少个线程
    public MyThreadPoolExecutor(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                // 线程要做的事情就是把任务队列中的任务不停的取出来, 并且进行执行
                while (true) {
                    try {
                        // 此处的 take 带有阻塞功能的.
                        // 如果队列为 空, 此处的 take 就会阻塞.
                        Runnable runnable = queue.take();
                        // 取出一个任务就执行一个任务即可
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            threadList.add(t);
        }
    }

        线程里面,取出一个任务就执行这个任务,如果队列里没有任务,就会阻塞等待,等有任务,再执行任务,如此循环往复;每创建一个线程,都要放进链表中,也要记得start,开启线程。

3.4 存放线程的链表--->每创建一个线程都放进链表中,这样也能让我们找到某个线程 

//存放线程的链表
List<Thread> list = new ArrayList<>();

3.5 完整版代码

class MyThreadPoolExecutor {
    private List<Thread> threadList = new ArrayList<>();

    // 就是一个用来保存任务的队列.
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    // 通过 n 指定创建多少个线程
    public MyThreadPoolExecutor(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                // 线程要做的事情就是把任务队列中的任务不停的取出来, 并且进行执行
                while (true) {
                    try {
                        // 此处的 take 带有阻塞功能的.
                        // 如果队列为 空, 此处的 take 就会阻塞.
                        Runnable runnable = queue.take();
                        // 取出一个任务就执行一个任务即可
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            threadList.add(t);
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

public class ThreadDemo33 {
//指定线程池的数目为4个线程,添加1000次任务到阻塞队列中,
//让着4个线程从阻塞队列中拿任务,再执行任务
//任务:打印0~1000,并显示是哪个线程打印的;
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
        for (int i = 0; i < 1000; i++) {
            int n = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务" + n + " , 当前线程为: " + Thread.currentThread().getName());
                }
            });
        }
    }
}

        结果如下:

ps:关于线程池案例的内容就到这里了,如果大家感兴趣的话,就请一键三连哦!!!

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

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

相关文章

JS新手入门笔记整理:条件判断

判断语句&#xff1a;IF 单向判断&#xff1a;if... 语法 if&#xff08;条件&#xff09; {…… } 如果“条件”返回结果为true&#xff0c;则会执行大括号{}内部的程序&#xff1b;如果“条件”返回结果为false&#xff0c;则会直接跳过大括号{}内部的程序&#xff0c;然后…

Python中User-Agent的重要作用及实际应用

摘要&#xff1a; User-Agent是HTTP协议中的一个重要字段&#xff0c;用于标识发送请求的客户端信息。在Python中&#xff0c;User-Agent的作用至关重要&#xff0c;它可以影响网络请求的结果和服务器端的响应。将介绍User-Agent在Python中的重要作用&#xff0c;并结合实际案…

编码器原理详解

编码器 什么是编码器 编码器可以用来将信息编码成为二进制代码&#xff0c;有点类似于取代号&#xff0c;人为的将二进制代码与对应的信息联系起来。 如下图所示&#xff1a; 假设有这三种情况会发生&#xff0c;且每次只发生一种情况 为了给这三种情况做一个区分&#xff…

FPGA - 240102 - FPGA期末速成

TAG - F P G A 、期末、速成 FPGA、期末、速成 FPGA、期末、速成 // – 习题1 – //CPLD&#xff08;Complex Programmable Logic Device&#xff09;是 Complex PLD 的简称&#xff0c;一种较 PLD 为复杂的逻辑元件。CPLD 逻辑资源多寄存器少&#xff0c;FPGA 逻辑弱而寄存器…

TemporalKit的纯手动安装

最近在用本地SD安装temporalkit插件 本地安装插件最常见的问题就是&#xff0c;GitCommandError:… 原因就是&#xff0c;没有科学上网&#xff0c;而且即使搭了ladder&#xff0c;在SD的“从网址上安装”或是“插件安装”都不行&#xff0c;都不行&#xff01;&#xff01;&am…

PMP考试的通过标准是什么?

PMP 新考纲一共是 180道题&#xff0c;答对 108道就通过了&#xff0c;具体怎么看&#xff1f; 登录网站查询&#xff1a; 第一步&#xff1a;登录PMI网站&#xff1a;www.pmi.org 第二步&#xff1a;进入Dashboard&#xff1a; 第三步&#xff1a;点击菜单Certifications - …

C#自动删除20天前文件夹图片

资料夹如下&#xff0c;需求为自动删除20天前保存的图片 如下为该方法函数&#xff0c;保留天数可以自定义 public static void CleanFile(){string path $"{SvMaster.DataPath}\\Image";\\文件夹路径DirectoryInfo dir new DirectoryInfo(path);FileSystemInfo[] …

XD6500S一款串口SiP模块 射频LoRa芯片 内置sx1262

1.1产品介绍 XD6500S是一款集射频前端和LoRa射频于一体的LoRa SIP模块系列收发器SX1262 senies&#xff0c;支持LoRa⑧和FSK调制。LoRa技术是一种扩频协议优化低数据速率&#xff0c;超长距离和超低功耗用于LPWAN应用的通信。 XD6500S设计具有4.2 mA的有效接收电流消耗&#…

一创聚宽停止服务,散户可以选择它!

第一创业和聚宽停止合作&#xff01;还有什么量化平台可选&#xff1f; 先了解背景&#xff1a; 从2023年9月25日起&#xff0c;一创聚宽的量化交易平台暂停开通交易权限&#xff0c;到2023年12月29日&#xff0c;一创聚宽量化交易平台将停止提供所有服务。这一消息对于之前使…

IPv6有状态地址自动配置(DHCPv6)

IPv6有状态地址自动配置 IPv6实现了对无状态地址自动配置的支持。这种不需要特殊服务器的地址自动配置方式有着极大的好处。使用起来也很方便,而在IPv4时代曾经是地址自动分配的首选方式的DHCP好像显得有些没落了。但是,DHCP作为有状态地址自动配置的方式之一,依旧有着无状…

echarts实现点击不同的柱子实现类目的不同名字

实现效果如下图: 首先实现echarts堆叠柱状图数据为0的不占用x轴空间 option {tooltip: {trigger: axis,axisPointer: {type: shadow},},legend: {},grid: {left: 3%,right: 4%,bottom: 3%,containLabel: true},xAxis: [{type: category,position: bottom,data: [园区内, 园区…

3C电子制造:智慧物流引领产业升级

在当今科技飞速发展的时代&#xff0c;3C电子制造行业正面临着一系列挑战和机遇。市场需求的多变和技术革新的加速&#xff0c;使得企业必须不断创新和升级。在这个过程中&#xff0c;智慧物流成为了一个关键的环节&#xff0c;它能够有效地提高生产效率、降低成本并增强企业的…

无心剑小诗《银婚颂》

银婚颂 二十五个春秋共一轮 你是岁月赠予我最亮的星辰 从青春燃烧到岁月沉稳 你的笑颜,我永恒的晨昏 春花烂漫是你眼里的璀璨 夏日蝉鸣是彼此故事的和弦 秋叶纷飞诉说漫天情缘 冬雪纯洁见证温暖的牵绊 月光洒满每段共享小径 星光点染每个深情的夜晚 风雨同舟铸就铜墙铁壁 携…

专属定制适合个人的知识付费平台,打造个性化品牌与自主管理体验

明理信息科技知识付费saas租户平台 在当今数字化时代&#xff0c;知识付费平台已经成为人们获取专业知识、提升自身素质的重要渠道。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。因此&#xff0c;我们提出了专属定…

易基因:ChIP-seq等揭示Runx2通过转录调控Itgav表达激活肝星状细胞以促进肝纤维化|科研进展

这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 肌成纤维细胞&#xff08;myofibroblasts&#xff09;主要由肝脏中活化的肝星状细胞(hepatic stellate cells HSC)组成&#xff0c;在肝纤维化进展中发挥着核心作用。由于肌成纤维细胞主要负责细胞外基质蛋…

neo4j图数据库安装和测试

neo4j图数据库安装和测试 1. 下载合适的neo4j软件版本。 https://we-yun.com/doc/neo4j/ https://neo4j.com/deployment-center/#enterprise 2. 下载JAVAJDK 由于neo4j是一个用Java编写的图形数据库&#xff0c;因此在安装和运行Neo4j之前&#xff0c;需要先安装Java Developm…

H5定时打卡领取奖励系统PHP源码,附带搭建教程

搭建教程 环境采用&#xff1a;linux的服务器系统 Nginx 1.18.0 MySQL 5.6.49 PHP-7.2 创建一个网站绑定上域名PHP版本选择7.2 这里不要绑定错了 创建好&#xff0c;我们进入到网站根目录&#xff0c;把源码打包压缩上传进去 压缩格式要用zip 不能有中文&#xff0c;…

《软件项目接口安全设计规范》

1.token授权机制 2.https传输加密 3.接口调用防滥用 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计 软件全套文档&#xff1a;软件开发全套资料-CSDN博客

javaweb项目jar包部署

装好系统后直接先安装java环境 方法一&#xff1a;使用OpenJDK安装Java 更新系统软件包列表&#xff1a; sudo yum update安装OpenJDK&#xff1a; sudo yum install java-1.8.0-openjdk-devel # Java 8安装如果你需要其他版本的Java&#xff0c;你可以替换 1.8.0 为其他版本…

奇数码问题

title: 奇数码问题 date: 2024-01-05 11:52:04 tags: 逆序对 cstefories: 算法进阶指南 题目大意 解题思路 将二维转化为一维&#xff0c;求他的逆序对&#xff0c;如果逆序对的奇偶性相同&#xff0c;则能够实现。 代码实现 #include<iostream> #include<string.h&…