一篇带你彻底搞懂线程池

news2024/9/21 4:36:34

目录

一、自定义线程池

1、产生背景

2、堵塞队列

3、线程池

4、拒绝策略

二、ThreadPoolExecuor

1、线程池状态

2、构造方法

3、newFixedThreadPool

4、newCachedThreadPool

5、newSingleThreadExecutor

6、提交任务

7、关闭线程池

三、异步模式之工作线程

1、定义

2、饥饿

3、解决

四、多少线程数合适(重点)

1、CPU密集型运算

2、I/O密集型运算

五、任务调度线程池

1、Timer类

2、ScheduledThreadPoolExecutor

3、定时任务

六、Tomcat线程池

1、介绍

2、参数


一、自定义线程池

1、产生背景

线程是系统资源,每创建新的线程都会占用系统内存(分配栈内存),如果高并发场景下,为每个任务都创建个线程(可能out of memory),而且cpu也忙不过来, 会出现频繁上下文切换

我们在学juc提供的线程池之前,先自定义线程池,主要有3部分,thread pool线程池、blocking queue阻塞队列和main消费者

2、堵塞队列

实现阻塞队列首先要有个任务队列queue、锁、生产者条件变量、消费者条件变量、容量。然后阻塞获取的方法,阻塞添加的方法,获取大小的方法。

阻塞获取:上来先循环如果队列是空,说明没有线程可以用就直接调用消费者条件变量休息室去等待,当别人往里面添加了元素就会唤醒一个,然后他就去队列移除一个元素再唤醒一个生产者休息室里的一个,最后返回。

阻塞添加:进来如果队列满了就进入生产者休息室,当消费者移除队列一个线程后会通知他,他就添加一个线程,之后再唤醒消费者。

带超时的阻塞获取:参数带一个超时时间时间单位,先进来调用toNanos方法把时间统一转化为纳秒,然后其他都跟刚刚获取一样,就每次尝试获取是调用awaitNanos返回的是剩余时间,然后循环再进来判断最后超过时间就返回null

带超时的阻塞添加:传入任务对象、超时时间和单位,跟上面是一样的

3、线程池

属性有刚刚我们写的阻塞队列,还有线程集合用hashSet,我们的线程就不用thread了我们包装了个worker类,还有核心线程数,获取任务的超时时间时间单位

执行任务的方法是没有超过核心线程数的时候就交给worker去执行任务,如果超过就往任务队列放任务。

这个worker的run方法就是执行任务的,当任务不为空就执行任务 ,当task执行完毕就从任务队列里面获取执行。当所有任务都执行完成了就直接从集合this中移除

4、拒绝策略

利用策略模式来实现当任务队列满的拒绝策略

tryPut方法直接传入用户输入的函数式变成,然后判断队列是否满,满了就调用用户的方法,如果没有满就加入任务队列。

让用户自己定义拒绝策略

二、ThreadPoolExecuor

1、线程池状态

ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量

状态名高3位接收新任务处理队列任务说明
running111YY
shutdown000NY不会接受新任务了但是会处理堵塞队列中的剩余任务
stop001NN会中断正在执行的任务,并抛弃阻塞队列的任务
tidying010--任务全部执行完毕,活动线程为0即将进入终态
terminated011--终结状态

从数字上比较,terminated>tidying>stop>shutdown>running

注意:这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程池数量合二为一,这样可以用一次cas原子操作进行赋值,从而可以减少一次cas的原子操作

2、构造方法

  • corePoolSize 核心线程数目最多保留的线程数
  • maximumPoolSize 最大线程数目
  • keepAliveTime 生存时间针对救急线程
  • unit 时间单位针对救急线程
  • workQueue 阻塞队列
  • threadFactory 线程工厂(创建线程的可以起名字)-特定的名字后面调试就方便
  • handler 拒绝策略 

救济线程:数量就是最大线程数减去核心线程数,当我们任务来的时候先用核心线程,用完了就进入阻塞队列(前提是有界队列),当队列满了会看看有没有救济线程有了就用救济线程,没有才拒绝策略。当救济线程使用完毕后会看生存时间,当超过生存时间还没有人用到就会销毁,但核心线程不会。

拒绝策略:如果线程阻塞队列满且没救济线程就会拒绝策略,jdk提供了4种实现,其他著名的框架也提供了实现

  • AbortPolicy 让调用者抛出RejectedExecutionException异常,这是默认策略
  • CallerRunsPolicy 让调用者运行任务
  • DiscardPolicy 放弃本次任务
  • DiscardOldestPolicy 放弃队列中最早的任务,本任务取代之
  • Dubbo的实现,在抛出RejectedExecutionException异常之前会记录日志,并dump线程栈信息,方便定位问题
  • Netty的实现,创建一个新线程来执行任务(不太好,达不到限制线程的目的)
  • activeMQ的实现,带超时时间等待60s尝试放入队列,类似我们之前自定义的拒绝策略
  • PinPoint的实现,他使用了拒绝策略链,会逐一尝试策略链中每种拒绝策略

3、newFixedThreadPool

特点:

  • 核心线程数==最大线程数没救济线程被创建)因此也无需超时时间
  • 阻塞队列是无界的,可以放任意数量的任务

评价:适用于任务量已知,相对耗时的任务

4、newCachedThreadPool

带缓冲功能的线程池

特点:

核心线程数是0最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着全部都是救济线程(60s后可以回收),救急线程可以无限创建

队列采用synchronousQueue实现特点是,他没有容量,没有线程来取是放不进去的(一手交钱,一手交货)

评价:整个线程池表现为线程数会根据任务量不断增加,没有上线,当任务执行完毕,空闲1分钟后释放线程,适合任务数比较密集,但每个任务执行时间比较短的情况。

5、newSingleThreadExecutor

使用场景:

希望多个任务队列排队执行,线程数固定位1,任务多于1就会进入无界队列阻塞,任务执行完毕,这是唯一的线程也不会被释放。

区别:

  • 自己创建一个单线程:串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证线程池的正常工作。
  • 固定大小线程池初始化为1:以后还可以修改,对外暴露的是ThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改。
  • 单例线程池:线程个数始终为1,不能修改,FinalizableDelegatedExectorService应用的是装饰器模式,多用FinalizableDelegatedExectorService做了一个装饰封装,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecuotr中特有的方法

6、提交任务

execute:传入runnbale,不需要返回值,最基础的执行

submit:这种传入的是callable返回的是futrue对象,利用的是保护性暂停模式,这种就是主线程会阻塞等待futue的响应结果,有就返回。

invokeAll:接收一个callable任务的集合,返回的也是一个futrue的集合;还有一种接收3个参数的构造器,就是多个超时时间单位。主线程会阻塞等待集合全部任务全部执行完成

invokeAny:接收一个callable集合,然后他不会全部执行完成,当第一个最快执行完成的执行完了就返回,其他任务取消,所以返回的只是一个object,不是上面的list了。之前那个从多个接口获取结果,拿最快的那个应该可以用这种。

7、关闭线程池

shutdown(),线程池的状态会变成shutDown不会接收新的任务,但是已经提交的任务会执行完,此方法不会阻塞等那些要运行完的运行完,而是直接掉用shotdown就返回

showdownNow(),线程池状态会变为stop不会接收新任务,会将任务队列中的任务返回,并用interrupt的方式中断正在运行的任务

 

三、异步模式之工作线程

1、定义

让有限的工作线程worker thread来轮流异步处理无限多的任务,也可以将其归类为分工模式,他的典型实现就是线程池,也体现了经典的设计模式中的享元模式

例如,海底捞的服务器,轮流处理每位客人点餐,如果每个客人都分配一个服务员,成本就太高了

注意:不同的任务类型应该使用不同的线程池,这样能避免饥饿,并提升效率

例如:如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务B)显然效率不咋样,分成服务员(线程池A)与厨师(线程池B)更为合理

2、饥饿

固定大小的线程池会有饥饿现象

两个工人是同一个线程池中的两个线程,他们要做的事情是:为客人点餐和后厨做菜,这是两个阶段

  • 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
  • 后厨做菜:没啥说的,做就是了

比如工人A处理点餐任务,接下来他要等工人B把菜做好,然后上菜,他们配合的蛮好,但现在同时来两个客人,这个时候A和B都去处理点餐了,没人做菜了,这个时候就饥饿

3、解决

应该把不同的任务交给不同的线程池去处理,这样就不会导致这种都做一个任务另一个做不了导致的饥饿问题。

四、多少线程数合适(重点)

过小会导致不能充分利用系统资源、容易饥饿

过大会导致更多线程上下文切换,占用更多内存

1、CPU密集型运算

这种的瓶颈往往在于线程上下文切换,所以我们尽可能避免他。通常采用 CPU核心数+1 能够实现最优CPU利用率,+1是保证线程由于页缺失故障(操作系统)或其他原因导致暂停,额外的这个线程可以顶上去,保证CPU时钟周期不被浪费

2、I/O密集型运算

CPU不总处于繁忙状态,例如当执行业务计算时,这个时候会用cpu资源,但当执行IO操作时,远程RPC调用时,包括进行数据库操作时,这时候CPU就闲下来了,就可以利用多线程提高他的利用率,经验公式如下:

线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间+等待时间)/ CPU计算时间

五、任务调度线程池

1、Timer类

在任务调度线程池加入前,可以用Timer类来实现定时功能,Timer的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务

2、ScheduledThreadPoolExecutor

用这种线程池在只有1个线程的时候,当第一个任务异常是不会影响到第二个任务的。

他除了延迟执行外还可以定时执行任务,每隔一段时间执行一次,用的是scheduleAtFiexedRate方法,构造器是任务、初始延迟、间隔时间、时间单位。但是如果任务本来的执行时间就很长大于间隔时间的话,他的下一个任务不会间隔了变成仅挨着执行,可以保证任务不会重叠,但是没有间隔了。

还有个scheduleWithFixedDelay()中间的参数是每个任务的间隔时间,这个才是正在的定时间隔的任务,他的当任务超过了间隔时间了,还是会间隔固定时间才会执行下一个,他的delay是从上一个任务的结束时间开始算的。

线程池中处理异常的两种方式:

  1. try:直接用try catch手动去抓异常
  2. 用submit方式拿到future对象:主线程去调用future的get方法获取如果成功就会返回值,如果中间出现了异常会返回异常信息

3、定时任务

主要就是用LocalDataTime的时候算出目标的执行时间,用那个时间减去现在的时间就是距离开始的时间(第一个参数),第二个参数就间隔的时间,然后单位。

六、Tomcat线程池

1、介绍

Tomcat分为连接器和容器部分:

  • 连接器就是为了连接,这里就用到了线程池(我们就讲连接器的线程池)
  • 容器部分可以实现servlet规范,运行组件

  • LimitLatch 用来限流,可以控制最大连接个数,类似JUC中的Semaphore
  • Accept 只负责接收新的socket连接(是个线程不断循环接受连接)
  • Poller 只负责监听socket channel是否有可读IO事件(也是个线程负责不断循环处理是否有可读IO事件发生)一旦可读,封装一个任务对象socketProcessor,提交给Executor线程池处理
  • Executor 线程池中的工作线程最终负责处理请求

tomcat线程池扩展了原本的ThreadPoolExecutor,行为略微改了一带你,他重现写了一下那个execute方法,如果总线程数超过最大线程数,不会立刻拒绝策略抛出异常,而是再次尝试,如果还是失败才是抛出异常。

源码:他是直接用原本的方法先,如果抛出异常了说明超过最大线程数的拒绝策略了,他直接catch住然后重新尝试加入阻塞队列,如果没法加入自己再抛出 

2、参数

默认是只有Connector来配置的,但是我们也可以去配置文件注解打开Executor如果有的话,就Executor为准

这个是反过来的,当提交任务大于核心线程就会判断是否小于最大线程,如果小于最大线程是用救济线程的,超过才加入阻塞队列,然后队列满是Integer最大值,接近无界队列。

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

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

相关文章

C-数据的储存(上)

文章目录 前言🌟一、数据类型详细介绍🌏1.内置类型💫(1).整形家族💫(2).浮点数家族🌏2.构造类型(也称自定义类型)🌏3.指针类型&#x…

OpenCV 入门教程:Haar特征分类器

OpenCV 入门教程: Haar 特征分类器 导语一、Haar特征分类器原理二、Haar特征分类器步骤三、示例应用总结 导语 Haar 特征分类器是图像处理中常用的目标检测算法,用于识别图像中的特定目标。该算法基于 Haar-like 特征模板,通过训练分类器来实…

ArcGIS PRO基础教程(一)

操作要求 1.面积为50-80亩 2.不能选在有耕地、园地内 3.坡度小于15度,高程在以下1930 4.距离水源地在300米以内 已知数据 1.等高线图 CONTOUR 2.土地利用图 parcel 3.水系图 water 操作步骤 创建工程,模板选地图就可以了(注:在arcgis pro中创建工程可以看作在arcg…

大火的ChatGPT与表格插件结合会有哪些意想不到的效果?

大火的ChatGPT与表格插件结合会有哪些意想不到的效果? 摘要:本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 ChatGPT已经火了好…

前端全集Ⅰ---- HTML/CSS/JavaScript

一 介绍web开发 Web:全球广域网,也称万维网,能够通过浏览器访问的网站 Web网站的工作流程:(前后端分离模式) 网页有哪些组成? 文字、图片、视频、音频、超链接 前端代码通过浏览器的解析和渲…

3-exercises

解: (1)Create a tensor a from list(range(9)). Predict and then check the size, offset, and stride. 创建列表a 将其转化为张量 a.size:The size (or shape, in NumPy parlance) is a tuple indicating how many elements a…

脚本引流是什么?其实很好理解,就是利用软件脚本来引流,这种软件我们通常叫引流脚本

脚本引流是什么?其实很好理解,就是利用脚本来引流,这种软件我们通常叫引流脚本,引流脚本的研发就是结合了以往的那些加人软件,从中吸取了长处并且升级了功能,而且通过不断的测试改进,在今年的7月…

C# PaddleInference OCR文字识别(只识别)

说明 C# PaddleInference OCR文字识别(只识别),没有文字区域检测、文字方向判断 测试图片是文字区域检测裁剪出来、处理过的图片 完整的OCR识别查看 C# PaddleInference OCR识别 学习研究Demo_天天代码码天天的博客-CSDN博客 效果 项目 …

-1在内存中的存储及打印问题。

首先先看看代码: #include"stdio.h" int main() { char a -1; signed char b -1; unsigned char c -1; printf("a%d b%d c%d", a, b, c); return 0; } 代码很简单,问打印结果是什么? 下面我…

Java 比对两张图片的差异

1.基本介绍 Github上的“https://github.com/akullpp/awesome-java”页整理了非常多的各类Java组件的实现,前面一篇从它的图片处理篇找到了《image-comparison》进行了动手实践,关于图片处理的二维码组件《ZXing》本站曾有实践;关于图片识别…

CUDA+CUDNN+torch+torchvision安装

弄了好久,终于弄好了!!! 原因:其实之前我是已经配置好pytorch的相关环境的了。但是这段时间,在跑GNN相关论文中的代码时,发现代码中的某个函数要求torch必须得是1.8 而我之前安装的是torch1.1…

leetcode-209.长度最小的子数组

leetcode-209.长度最小的子数组 文章目录 leetcode-209.长度最小的子数组题目描述代码提交(快慢指针-滑动窗口) 题目描述 代码提交(快慢指针-滑动窗口) 代码 class Solution {public:int minSubArrayLen(int target, vector<int> &nums) {int slow 0;int fast 0;i…

Spring中事务传播机制的理解与简单试用

目录 一&#xff0c;前言 二&#xff0c;Spring框架中的事务传播行为 三&#xff0c;事务的传播行为测试 Propagation.REQUIRED Propagation.SUPPORTS Propagation.MANDATORY Propagation.REQUIRES_NEW Propagation.NOT_SUPPORTED Propagation.NEVER Propagation.NES…

c++11 标准模板(STL)(std::basic_istream)(三)

定义于头文件 <istream> template< class CharT, class Traits std::char_traits<CharT> > class basic_istream : virtual public std::basic_ios<CharT, Traits> 类模板 basic_istream 提供字符流上的高层输入支持。受支持操作包含带格式的…

从零配置 linux 开发环境

文章目录 目的效果图配置本地 Windows 主机好用工具WSLSSH 连接远程 Linux 开发机配置本机字体【in-prog】配置 vscode 远程连接 配置远程 Linux 主机zsh & oh-my-zsh配置 github 的 SSHneovimvundleinit.vim 文件 vim-plug.lua 文件 tmuxclangcpplint 目的 记录下我的开发…

Go语言开发者的Apache Arrow使用指南:高级数据结构

经过对前面两篇文章《Arrow数据类型》[1]和《Arrow Go实现的内存管理》[2]的学习&#xff0c;我们知道了各种Arrow array type以及它们在内存中的layout&#xff0c;我们了解了Go arrow实现在内存管理上的一些机制和使用原则。 Arrow的array type只是一个定长的、同类型的值序列…

[SWPUCTF 2021 新生赛]jicao

点进去后是一段php代码 <?php highlight_file(index.php); include("flag.php"); $id$_POST[id]; $jsonjson_decode($_GET[json],true); if ($id"wllmNB"&&$json[x]"wllm") {echo $flag;} ?> 包含了flag.php文件&#xff0c;设定…

数据结构关键路径问题:下面是一个有10个活动的AOE图,时间余量最大的活动是()

关键路径问题 名人说&#xff1a;莫听穿林打叶声&#xff0c;何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 本篇笔记整理&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 关键路径问题〇、概念说明1、AOE网2、关键路…

4-软件错误(BUG)

目录 1.什么是bug? 2.如何描述一个bug? ①发现问题的版本 ②问题出现的环境 ③错误重现的步骤 ④预期行为的描述 ⑤错误行为的描述 ⑥其他 ⑦不要把多个bug放到一起 PS&#xff1a;案例1 PS&#xff1a;案例2 3.如何定义bug的级别&#xff1f; ①Blocker&#x…

FFmpeg5.0源码阅读—— avcodec_send_packetavcodec_receive_frame

摘要&#xff1a;本文主要描述了FFmpeg中用于解码的接口的具体调用流程&#xff0c;详细描述了该接口被调用时所作的具体工作。   关键字&#xff1a;ffmpeg、avcodec_send_packet、avcodec_receive_frame   读者须知&#xff1a;读者需要了解FFmpeg的基本使用流程&#xf…