线程池技术总结

1. 线程池解决了什么问题

线程池是集中管理线程的,以实现线程的重用,降低资源消耗,提高响应速度,提高线程的可管理性等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。使用线程池可以进行统一的分配,调优和监控。使用线程池的优势

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性

2. 线程池如何工作

使用线程池可以进行统一的分配,调优和监控。提交一个任务到线程池中,线程池的处理流程如下:

1、判断核心线程池里的核心线程(corePoolSize)是否已满

  • 是,判断工作队列是否已经满了
  • 否,不管核心线程有没有空闲,都创建一个新的线程执行这个任务

2、工作队列是否已经满了

  • 已满,判断线程池是否满了
  • 未满,将任务存储在工作队列里

3、判断线程池是否满了【核心线程以外的可以创建的线程,最大线程数】

  • 已满,交给饱和策略handle处理无法执行的任务
  • 未满,创建一个新的线程执行这个任务

4、调用executor()方法中创建一个线程,会让这个线程执行当前任务,线程在执行完当前任务以后,队列里有任务就会不停的从BlockingQueue中取任务执行。

(图片来自网络)

3. 线程池如何创建

线程池的真正实现类是 ThreadPoolExecutor,我们可以实现ThreadPoolExecutor 自定义线程池的方式,其次我们也可以使用Executor框架的 Executors类(java.util.concurrent包)创建封装好的线程。而ThreadPoolExecutor 类参数最多,并且 Executors 封装的线程池底层都是基于ThreadPoolExecutor实现的。

1、使用 Executors 工具类

什么是Executors?Executors框架实现的就是线程池的功能。Executors工厂类中提供的newCachedThreadPool、newFixedThreadPool 、newScheduledThreadPool 、newSingleThreadExecutor 等方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景

1.newFixedThreadPool 

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

特点:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。

缺点:线程数量是固定的,但是阻塞队列是无界队列。如果有很多请求积压,阻塞队列越来越长,容易导致OOM(超出内存空间)

总结:请求的挤压一定要和分配的线程池大小匹配,定线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

2.newSingleThreadExecutor 

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

特点:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,他必须保证前一项任务执行完毕才能执行后一项。保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

缺点:缺点的话,很明显,他是单线程的,高并发业务下有点无力

总结:保证所有任务按照指定顺序执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它

3.newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。如果工作线程空闲 60 秒没有被使用,会自动关闭。

特点:newCachedThreadPool创建一个可缓存线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的

长度作任何限制

缺点:他虽然可以无线的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问

题的点,就可以去重写一个方法限制一下这个最大值

总结:线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

4.newScheduledThreadPool 

创建一个定长线程池,支持定时及周期性任务执行。

特点:创建一个固定长度的线程池,而且支持定时的以及周期性的任务执行,类似于Timer(Timer是Java的一个定时器类)

缺点:由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。

2、使用 ThreadPoolExecutor 

参数介绍如下:

  1. corePoolSize:线程池中的常驻核心线程数。如果调用了线程池的prestartAllCoreThreads 方法,线程池会提前创建并启动所有基本线程。
  2. maximumPoolSize:线程池允许创建的最大线程数,此值必须大于等于1。
  3. keepAliveTime:多余的空闲线程的存活时间。当前线程池的数量超过 corePoolSize 时,当空闲时间达到 keepAliveTime 值时,多余空闲线程会被销毁直到只剩下 corePoolSize 个线程为止。
  4. unit :keepAliveTime 的单位。可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
  5. threadFactory:用于设置创建线程的工厂,可通过该工厂给线程重命名等等。一般使用默认的即可。
  6. workQueue:等待队列,用于存放被提交但尚未执行的任务。
  7.  handler:拒绝策略。当队列满了,并且工作线程数量大于等于线程池的最大线程数(maximumPoolSize)时,拒绝请求执行的 runnable 的策略。

拒绝策略说明

  • AbortPolicy:直接抛出 RejectedExecutionException 异常来拒绝新任务的处理,这是默认策略。
  • CallerRunsPolicy:用调用者所在的线程来执行任务。调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果应用程序可以承受此延迟并且不能任务丢弃任何一个任务请求的话,可以选择这个策略
  • DiscardOldestPolicy:丢弃阻塞队列中最靠前(等待最久)的任务,并执行当前任务(ThreadPoolExecutor.execute(runnable))。
  • DiscardPolicy:不处理新任务,直接丢弃任务

3、注意事项

通过以上两种方式都可以得到一个线程池,日常开发中建议使用第 2 种方式来创建线程池。在阿里巴巴Java开发手册中提到不允许适用Executors创建线程,而是使用ThreadPoolExecutor方式明确线程池运行规则

4. 线程池如何关闭

我们可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池,但是它们的实现原理不同。

1、shutdown 

线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。原理是只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。

2、shutdownNow 

线程池拒接收新提交的任务,同时立刻关闭线程池,线程池里的任务不再执行。原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow 会首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

只要调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow。

5.线程池如何配置线程数 

要合理的分配线程池的大小要根据实际情况来定,简单的来说的话就是根据CPU密集和IO密集来分配

1、什么是CPU密集

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开

几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那样。

2、什么是IO密集

IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,

即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

3、如何设置线程数

分配CPU和IO密集:

  • CPU 密集型任务配置尽可能少的线程数量,如配置 Ncpu+1 个线程的线程池。
  • IO 密集型任务则由于需要等待 IO 操作,线程并不是一直在执行任务,则配置尽可能多的线程,如 2*Ncpu。

网上能很容易查到以上参考值,真正使用时候还需要考虑以下场景,并在观察一段执行情况后做出优化调整,经验永远大于理论

  • 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
  • 任务的依赖:是否依赖其他系统资源,如数据库连接等。
  • 任务的环境:CPU是几核的,CPU是否支持超线程,CPU是否也同时执行其他任务

前一篇:使用Arthas排查性能问题

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (147)-- 算法导论12.2 2题

二、用go语言,写出 TREE-MINIMUM 和 TREE-MAXIMUM 的递归版本。 灵捷3.5 。 文心一言: 以下是用 Go 语言编写的树的最大值和最小值的递归版本代码: package main import ( "fmt" ) type TreeNode struct { Val int Le…

哈希表 LeetCode 2661. 找出叠涂元素

2661. 找出叠涂元素 给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 mat 。arr 和 mat 都包含范围 [1,m * n] 内的 所有 整数。 从下标 0 开始遍历 arr 中的每个下标 i ,并将包含整数 arr[i] 的 mat 单元格涂色。 请你找出 arr 中在 …

LeetCode105.从前序和中序遍历序列构造二叉树

这道题看完题想了几分钟就想到大概的思路了,但是在写的时候有很多细节没注意出了很多问题,然后写了1个多小时,其实这道题挺简单的。 首先,最基本的知识,先序遍历是根左右,中序遍历是左根右,那么…

重磅!GPT-4 API,全面开放使用!

7月7日,OpenAI在官网宣布,GPT-4 API全面开放使用。现所有付费API用户都可直接访问8K上下文的GPT-4,无需任何等待。 预计到7月底之前,OpenAI会向全新的开发人员开放GPT-4 API使用权限。(API详细使用说明地址&#xff1…

【AB平台数据建设】从实验平台到数据管道

文章目录 前言1.从AB实验平台聊起(1)AB平台在业务中的发挥那些作用(2)AB平台进行实验工作流介绍 2.实验平台底层数据管道最小MVP解构(1)数据管道数据从哪里来?(2)数据管道的输出数据有哪些? 小结 前言 AB实验平台是一种通过小范围放量,测试不…

安装两个WIN10/WIN11系统到两个盘中,第二个系统依赖原系统盘引导的问题

前段时间折腾装一个双系统,主要两个方面考虑: 1. 原来的系统又许多软件,想着先保留; 2. 系统想安装到一个固态硬盘中; 在安装的过程中遇到了一些问题,这里记录分享一下。 问题1,运行系统自动安装…

深入 C 语言和程序运行原理 实战项目代码在CentOS 7上编译

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core),uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64。 安装gtest 参考博客《使用gtest和lcov测试代码覆盖率》 wget https://github.com/google/googletest/archive/refs/…

Android NDK项目创建的时候C++版本选择都有什么区别

Android ndk项目在创建的时候有C版本选择有4个选项,分别是Toolchain default, C11,C14,C17。 C是一种广泛使用的编程语言,它不断地发展和更新,以适应不同的需求和场景。C的语言标准是由国际标准化组织&…

2.qml 3D-View3D类学习

本章我们来学习View3D类。 View3D是用来渲染3D场景并显示在2D平面的类,并且该类可以放在QML2D下继承于Item子类的任何场景中,比如将View3D放在Rectangle中: Rectangle {width: 200 height: 200color: "red"View3D { anchors.fill: parent…

九章量子计算机:引领量子计算的新篇章

九章量子计算机:引领量子计算的新篇章 一、引言 随着科技的飞速发展,量子计算已成为全球科研领域的前沿议题。九章量子计算机作为中国自主研发的量子计算机,具有划时代的意义。本文将深入探讨九章量子计算机的原理、技术特点、应用前景等方面,带领读者领略量子计算的魅力…

什么是OV SSL证书?

OV SSL证书是组织验证SSL证书的缩写,是三个SSL验证级别之一的名称。 OV是指实名类型的SSL证书,这个实名其实只要证明发布者身份就可以签发,无论是个人还是企业都可以进行申请。 SSL证书大家都知道就是用于网站地址的http改成https加密协议的…

gromacs学习及使用(1)

1.Gromacs的使用 2.Gromacs 的第一步_能量最小化 3.分子动力学模拟Gromacs一般使用步骤(空蛋白) 4.GROMACS优化(没看懂) 5.GROMACS快速入门(有好东西) GROMACS中文教程 gmx editconf -f xxx -o xxx6.GROMACS运行参数之em.mdp文…

Burp Suite序列之目录扫描

如果你是一名渗透测试爱好者或者专业人士,你一定知道目录扫描是渗透测试中非常重要的一步。通过目录扫描,我们可以发现网站的敏感信息,隐藏的功能,甚至是后台入口。目录扫描可以帮助我们更好地了解目标网站的结构和漏洞。 但是&a…

四大视角看EMC设计:滤波、接地、屏蔽、PCB布局

电磁干扰的主要方式是传导干扰、辐射干扰、共阻抗耦合和感应耦合。对这几种途径产生的干扰我们应采用的相应对策:传导采取滤波,辐射干扰采用屏蔽和接地等措施,就能够大大提高产品的抵抗电磁干扰的能力,也可以有效的降低对外界的电…

Shell循环:for(三)

示例:使用for实现批量主机root密码的修改 一、前提 已完成密钥登录配置(ssh-keygen)定义主机地址列表并了解远程修改密码的方法 [rootlocalhost ~]# ssh-keygen #设置免密登录[rootlocalhost ~]# ssh-copy-id 192.168.151.151 二、演示…

【趣味JavaScript】一文让你读懂JavaScript原型对象与原型链的继承,探秘属性的查找机制! 《重置版》

🚀 个人主页 极客小俊 ✍🏻 作者简介:web开发者、设计师、技术分享博主 🐋 希望大家多多支持一下, 我们一起学习和进步!😄 🏅 如果文章对你有帮助的话,欢迎评论 💬点赞&a…

【Linux】第二十三站:缓冲区

文章目录 一、一些奇怪的现象二、用户级缓冲区三、用户级缓冲区刷新问题四、一些其他问题1.缓冲区刷新的时机2.为什么要有这个缓冲区3.这个缓冲区在哪里?4.这个FILE对象属于用户呢?还是操作系统呢?这个缓冲区,是不是用户级的缓冲区…

STM32 自定义UART数据格式(串口通信点亮LED实验)

起始位:0xaa告诉机器我们要开始传输数据了。 校验位:等于前几项数据位的相加。 结束位:结束传输。 自定义UART数据格式: 1》CPU与CPU之间 2》外设与CPU之间 这里举例,利用串口调试助手发送一串数据,…

Java数据结构之《循环队列》题目

一、前言: 这是怀化学院的:Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究,问题基本解决,若有bug欢迎下方评论提出意见,我会第一时间改进代码,谢谢!) 后面其他编程题只要我写完…

11.26电梯控制器设计分析

项目三 电梯控制器设计(*****) 设计一个多楼层的电梯控制器系统,并能在开发板上模拟电梯运行状态。可以利用按键作为呼叫按键,数码管显示电梯运行时电梯所在楼层,led灯显示楼层叫梯状态。 就是初始默认在1楼&#xff0…