【JavaEE初阶】 线程池详解与实现

文章目录

  • 🌴线程池的概念
  • 🎄标准库中的线程池
  • 🍀ThreadPoolExecutor 类
    • 🚩corePoolSize与maximumPoolSize
    • 🚩keepAliveTime
    • 🚩ThreadFactory
    • 🚩workQueue
    • 🚩RejectedExecutionHandler handler
  • 🎍模拟实现线程池
  • ⭕总结

🌴线程池的概念

线程池,是一种线程的使用模式,它为了降低线程使用中频繁的创建和销毁所带来的资源消耗与代价。

通过创建一定数量的线程,让他们时刻准备就绪等待新任务的到达,而任务执行结束之后再重新回来继续待命。

想象这么一个场景:
在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营。店里没有雇人,而是每次有业务来了,就现场找一名同学过来把快递送了,然后解雇同学。这个类比我们平时来一个任务,起一个线程进行处理的模式。
很快老板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。老板还是很善于变通的,知道了为什么大家都要雇人了,所以指定了一个指标,公司业务人员会扩张到 3 个人,但还是随着业务逐步雇人。于是再有业务来了,老板就看,如果现在公司还没 3 个人,就雇一个人去送快递,否则只是把业务放到一个本本上,等着 3 个快递人员空闲的时候去处理。这个就是我们要带出的线程池的模式

线程池最核心的设计思路复用线程,平摊线程的创建与销毁的开销代价

相比于来一个任务创建一个线程的方式,使用线程池的优势体现在如下几点:

  • 避免了线程的重复创建与开销带来的资源消耗代价
  • 提升了任务响应速度,任务来了直接选一个线程执行而无需等待线程的创建
  • 线程的统一分配和管理,也方便统一的监控和调优

提升了响应速度是怎么体现的呢?

这里博主和大家详细说一下:

比如你去银行取钱
有两种方法:
方法一,我们自己可以在银行的取款机自己取,注意这时候我们是自主的,就像程序里的“用户态”。用户态执行的是程序员自己的代码,我想干嘛就干嘛,想在取款机里取钱、存钱、查询余额等都在我的掌控范围内
方法二,我们去柜台取钱,我们不能进入银行,只能交给柜员,让他执行你给的命令,间接完成,就像程序里的“内核态”。此时你你给银行内部人员发去了取钱的命令,注意此时她是立马给你取钱吗?她可能给同事闲聊几句、可能喝口水、锤锤背、或者领导叫她,这时候就会耽搁,你就只能等着,非常的被动,办理时间也变长了

此时呢,我们的线程池就像方法一,我们可以利用已经存在的线程自己完成操作,而不是去重新创建。

🎄标准库中的线程池

Executors 创建方式有以下几个

newFixedThreadPool创建固定线程数的线程池
newCachedThreadPool创建线程数目动态增长的线程池.
newSingleThreadExecutor创建只包含单个线程的线程池
newScheduledThreadPool设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer

注意上述方法是有返回值的

返回值类型为 ExecutorService

使用实例如下:

  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.

  • 返回值类型为 ExecutorService

  • 通过 ExecutorService.submit 可以注册一个任务到线程池中

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TestDemo1 {public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(10);for(int i = 1; i < 100;i++) {int n = i;pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("任务"+n);}});}}
}

结果展示:
在这里插入图片描述

🍀ThreadPoolExecutor 类

Executors 本质上是 ThreadPoolExecutor 类的封装.
在这里插入图片描述
我们也可以去
Java文档
进行查找
在这里插入图片描述
我们在图示区域找到java.util.concurrent包
在这里插入图片描述
这个包里面放的很多都与并法编程相关的类

接下来我们点击并找到ThreadPoolExecutor类
在这里插入图片描述
我们对这个类往下翻我们就可以看到它提供了一些构造方法
在这里插入图片描述
我们可以就看到ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定,接下来博主带大家看一下这些参数代表的意义(这里第四个构造方法参数最多,所以这里讲第四个构造方法的参数)

🚩corePoolSize与maximumPoolSize

  • corePoolSize代表的是核心线程数

  • maximumPoolSize代表的最大线程数

corePoolSize 指的是核心线程数,线程池初始化时线程数默认为 0,当有新的任务提交后,会创建新线程执行任务,如果不做特殊设置,此后线程数通常不会再小于 corePoolSize ,因为它们是核心线程,即便未来可能没有可执行的任务也不会被销毁。

随着任务量的增加,在任务队列满了之后,线程池会进一步创建新线程,最多可以达到 maximumPoolSize 来应对任务多的场景,如果未来线程有空闲,大于 corePoolSize 的线程会被合理回收。所以正常情况下,线程池中的线程数量会处在 corePoolSize 与 maximumPoolSize 的闭区间内。

就好比一个公司有很多员工,有正式员工,还有零时工

这两者的区别在于:

  • 正式员工是一定会存在的,而零时工是可以随时被辞退的

  • 公司忙的时候,需要员工就招募一些,活儿少了之后一些零时工可能就会被辞退

整体的策略为:正式员工打底,零时工动态调节

接下来我们讨论一个问题,实际开法当中,线程池中的线程数应该设置为多少合适?

答案为:不同的程序特点不同,此时设置的线程数也是不同的

这是为什么呢?

这里我们考虑两个极端情况:

  1. CPU密集型:每个线程要执行的任务都是通过狂转CPU来实现,此时的线程数最多不超过CPU的核数,此时,你设置的再大也没有用。

  2. IO密集型:每个线程干的工作就是等待IO(读写硬盘、读写网卡、等待用户输入·······),不吃CPU,此时这样的线程处于阻塞状态,不参与CPU调度,这时候多搞一些线程都无所谓,不再受制于CPU核数。理论上来说,可以设成无穷大(实际上当然是不行的)

然而,咱们再实际开放当中并没有程序符合这两种理想模型

真实的程序,往往一部分要吃CPU,一部分要等待IO

具体这个程序,几成工作量吃CPU,几成工作量等待IO,我们并不确定

那么我们实际开发中应该子怎么设置呢?

设置后进行测试,选择一个最优的选择

🚩keepAliveTime

当线程池中线程数量多于核心线程数时,而此时又没有任务可做,线程池就会检测线程的 keepAliveTime,如果超过规定的时间,无事可做的线程就会被销毁,以便减少内存的占用和资源消耗。

如果后期任务又多了起来,线程池也会根据规则重新创建线程,所以这是一个可伸缩的过程,比较灵活,我们也可以用setKeepAliveTime 方法动态改变 keepAliveTime 的参数值。

就相当于零时工如果闲下来太久,就会被辞退。setKeepAliveTime 可以设置空闲的时间

🚩ThreadFactory

ThreadFactory 实际上是一个线程工厂,它的作用是生产线程以便执行任务。我们可以选择使用默认的线程工厂,创建的线程都会在同一个线程组,并拥有一样的优先级,且都不是守护线程,我们也可以选择自己定制线程工厂,以方便给线程自定义命名,不同的线程池内的线程通常会根据具体业务来定制不同的线程名

🚩workQueue

workQueue是线程池的任务队列,作为一种缓冲机制,线程池会把当下没有处理的任务放入任务队列中,由于多线程同时从任务队列中获取任务是并发场景,此时就需要任务队列满足线程安全的要求,所以线程池中任务队列采用

BlockingQueue 来保障线程安全。常用的队列主要有以下几种:

  1. LinkedBlockingQueue
    LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
    这个队列需要注意的是,虽然通常称其为一个无界队列,但是可以人为指定队列大小,而且由于其用于记录队列大小的参数是int类型字段,所以通常意义上的无界其实就是队列长度为 Integer.MAX_VALUE,且在不指定队列大小的情况下也会默认队列大小为 Integer.MAX_VALUE。

  2. SynchronousQueue
    SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。拥有公平(FIFO)和非公平(LIFO)策略,使用SynchronousQueue阻塞队列一般要求maximumPoolSizes为无界(Integer.MAX_VALUE),避免线程拒绝执行操作。

  3. ArrayBlockingQueue
    ArrayBlockingQueue是一个有界缓存等待队列,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。

  4. DelayedWorkQueue
    DelayedWorkQueue 的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。

🚩RejectedExecutionHandler handler

在使用线程池并且使用有界队列的时候,如果队列满了,任务添加到线程池的时候就会有问题,那么这些溢出的任务,ThreadPoolExecutor为我们提供了拒绝任务的处理方式,以便在必要的时候按照我们的策略来拒绝任务

线程池拒绝任务的时机有以下两种:

  • 第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝。

  • 第二种情况是线程池没有能力继续处理新提交的任务,也就是工作已经非常饱和的时候。

线程池任务拒绝策略实现了 RejectedExecutionHandler 接口,JDK 中自带了四种任务拒绝策略。分别是AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy。其中AbortPolicy是ThreadPoolExecutor默认使用。

那他们分别代表是策略内容是什么呢?

  1. AbortPolicy(默认)
    这种拒绝策略在拒绝任务时,会直接抛出一个类RejectedExecutionException 的RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略

  2. DiscardPolicy
    这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

  3. DiscardOldestPolicy
    如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

  4. CallerRunsPolicy
    相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处:
    🎈第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。
    🎈第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。

🎍模拟实现线程池

接下来我们简单模拟实现一个简单的线程池

  1. 创建MyThreadPool实现我们的线程池

  2. 使用阻塞队列组织所有任务

  3. 构造方法里创建相应大小的线程数

  4. 提供一个submit方法使用线程池里面的线程

代码实现如下:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class MyThreadPool {// 此处不涉及到 "时间" , 此处只有任务, 就直接使用 Runnable 即可~~private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();// n 表示线程的数量public MyThreadPool(int n) {// 在这里创建出线程.for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}// 注册任务给线程池.public void submit(Runnable runnable) {try {queue.put(runnable);} catch (InterruptedException e) {e.printStackTrace();}}
}

测试代码:

public class TestDemo2 {public static void main(String[] args)  {MyThreadPool myThreadPool = new MyThreadPool(10);for(int i = 1; i < 100;i++) {int n = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("任务"+n);}});}}
}

测试结果:
在这里插入图片描述

⭕总结

关于《【JavaEE初阶】 线程池详解与实现》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

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

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

相关文章

PDF编辑阅读 PDF Expert v3.5.2

PDF Expert是由Readdle开发的一款专业的PDF编辑和阅读工具。它可以帮助用户在Mac、iPad和iPhone等设备上查看、注释、编辑、填写和签署PDF文档。 以下是PDF Expert的特点&#xff1a; PDF编辑&#xff1a;PDF Expert提供了丰富的PDF编辑功能&#xff0c;包括添加、删除、移动…

SQL基础语法总结(查询)

学习网站&#xff1a;https://www.w3schools.com/sql/&#xff0c;提供在线编程 以下内容仅SQL常见语法总结 数据 Customers表 Products表 OrderDetails表 Orders表 Shippers表 Employees表 选择查询 SELECT select语句用来从头数据库中选择数据 SELECT column1, column2,…

【论文阅读笔记】 Curated Pacific Northwest AI-ready Seismic Dataset

Curated Pacific Northwest AI-ready Seismic Dataset 太平洋西北部人工智能地震数据集 摘要 描述了一个AI就绪地震数据集包括各种地震事件参数 仪器元数据 地震波行描述地震目录和事件属性&#xff08;事件震级类型&#xff0c;信道类型&#xff0c;波形极性&#xff0c;信…

BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain

BadNets: Identifying Vulnerabilities in the Machine Learning Model Supply Chain----《BadNets:识别机器学习模型供应链中的漏洞》 背景&#xff1a; 许多用户将训练过程外包给云计算&#xff0c;或者依赖于经过训练的模型&#xff0c;然后根据特定的任务对模型进行微调。这…

SequenceFile、元数据操作与MapReduce单词计数

文章目录 SequenceFile、元数据操作与MapReduce单词计数一、实验目标二、实验要求三、实验内容四、实验步骤附&#xff1a;系列文章 SequenceFile、元数据操作与MapReduce单词计数 一、实验目标 熟练掌握hadoop操作指令及HDFS命令行接口掌握HDFS SequenceFile读写操作掌握Map…

Linux下Jenkins自动化部署SpringBoot应用

Linux下Jenkins自动化部署SpringBoot应用 1、 Jenkins介绍 官方网址&#xff1a;https://www.jenkins.io/ 2、安装Jenkins 2.1 centos下命令行安装 访问官方&#xff0c;点击文档&#xff1a; 点击 Installing Jenkins&#xff1a; 点击 Linux&#xff1a; 选择 Red Hat/…

文件系统相关

文件系统部分的大纲要求&#xff1a; 文件系统的全局结构&#xff1a;文件系统在外存中的结构&#xff0c;文件系统在内存中的结构外存空闲空间管理办法虚拟文件系统文件系统挂载 一、文件系统的层次结构 可分为三个层次&#xff1a;最低层是对象及其属性&#xff0c;中间层…

力扣:盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 …

C# Onnx Yolov8 Detect 指纹检测

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;namespace Onnx…

ShareMouse for Mac(多台电脑鼠标键盘共享软件)

ShareMouse mac版是一款Mac平台上可以在多台电脑间共享鼠标的工具软件&#xff0c;sharemousefor Mac支持 Windows 与 Mac&#xff0c;并可以在不同电脑间共享剪贴板。只需要移动鼠标指针的到想控制的显示器那里去、鼠标光标就会神奇地“跨越”到邻近的电脑屏幕上。每个计算机都…

linux安装新版本git2、配置github-ssh。(centos、aws)

一、安装Git 1、yum默认版本git #1.安装git sudo yum install git -y #2.确认Git已经安装成功 git --version如果要安装较新版本&#xff0c;可以安装一个repo &#xff0c;但是我这第一次尝试失败了&#xff0c;执行完提示找不到git2u&#xff0c;ius repo也连不上。而且每次…

FreeRTOS学习day1

顾名思义 免费的实时操作系统 用法基本和Linux下的多线程编程类似 探索者开发版实验 动态创建4个任务start_task task1 task2 task3 优先级依次为1 2 3 4 &#xff08;注意优先级不能为0,0是空闲任务&#xff09; 我的理解&#xff1a;主线程start_task 主线程 task1 ta…

doc与docx文档转html,格式样式不变(包含图片转换)

最近做一个富文本的需求&#xff0c;要求把文档内容转换到富文本内&#xff0c;文档中的格式也好&#xff0c;样式也好&#xff0c;图片啥的都要一致展示&#xff1b;踩了不少坑&#xff0c;据说word文档其实是一个压缩包&#xff0c;我不是特别清楚但是也能理解&#xff0c;自…

一、BurpSuite基本界面学习

一、Dashboard 1.Scan Scan功能&#xff1a;能够对网站进行爬行&#xff08;主要发现网站可交互的接口和网址关联的路径&#xff0c;结果显示在Target&#xff09;和代码审计&#xff08;除了爬行的效果外&#xff0c;一定程度上会进行漏洞发现与查找&#xff09; 重点&#…

Java代理

一、代理模式 代理模式定义&#xff1a;为其他对象提供一种代理以控制对这个对象的访问。 上面类图中有一个订单接口类(OrderService)、一个订单实现类(OrderServiceImpl)&#xff0c;订单模块的业务相对复杂和重要&#xff0c;如果这时候要在方法执行前后加上日志&#xff0c…

SD NAND对比TF卡优势(以CSNP4GCR01-AMW为例)

最近做的一个项目&#xff0c; 需要加大容量存储&#xff0c;这让我想到之前在做ARM的开发板使用的TF卡方案&#xff0c;但是TF卡需要携带卡槽的&#xff0c;但是有限的PCB板布局已经放不下卡槽的位置。 这个时候就需要那种能够不用卡槽&#xff0c;直接贴在板子上面&#xff0…

深度学习_3_实战_房价预测

梯度 实战 代码&#xff1a; # %matplotlib inline import random import torch import matplotlib.pyplot as plt # from d21 import torch as d21def synthetic_data(w, b, num_examples):"""生成 Y XW b 噪声。"""X torch.normal(0,…

【Ubuntu18.04】Livox Tele-15使用教程

Livox Tele-15使用教程 引言1 下载并安装Livox Viewer1.1 打开Livox Viewer程序1.2 设备ip链接 2 览沃Livox SDK二次开发并封装ROS2.1 览沃Livox SDK安装2.2 览沃Livox livox_ros_driver 安装2.3 Launch 配置文件描述 3 保存lvx格式的数据文件3.1 使用览沃Viewer保存lvx文件3.2…

vuecli2关于下载本地取环境变量没问题部署后路径取不到环境变量问题解决

功能 下载功能,用a标签实现下载 代码错误思路分析 解决办法可以忽略直接看下面 解决思路 在每个config文件中都定义了file_url,本地运行没有问题,但是部署到服务器开发环境, 拿到的是生产环境的FILE_URL地址 首先看了流水线-构建配置-编译配置-编译脚本里的打包命令: ** 本…

51单片机KeyWard

eg1&#xff1a; 单片机键盘的分类 键盘分为编码键盘和非编码键盘&#xff0c;键盘上闭合键的识别由专用的硬件编码器实现&#xff0c;并产生键编码号或键值得称为编码键盘&#xff0c;如计算机键盘&#xff0c;而靠软件来识别的称为非编码键盘&#xff0c;在单片机组成的各种…