Java 线程池之ThreadPoolExecutor学习总结

news2024/11/13 9:40:41

前提

java version "1.8.0_25"

池简述

软件开发活动中,我们经常会听到数据库连接池、内存池、线程池等各种“池”概念,这些“池”到底是什么东西呢?程序的世界里,我们可以将池简单的理解为一种容器类数据结构,比如列表。程序处理信息的过程中,可能会依赖某些资源或者对象(暂且统一称之为对象),比如数据库连接,来执行一些高频操作,比如数据表查询,此时,如果被依赖对象的存活时间比较短,那就意味着需要频繁的创建和销毁对象,这可能会很耗时、耗系统资源(CPU、内存、磁盘、网络等)。为了解决这个问题,进行程序设计时,可能会考虑在程序初始化时,预先创建一批所需对象,并存储到池中,或者根据需要即时创建对象,并在使用完成后,将对象添加到池中,这样,当程序需要(再次)使用对象时,可以直接从池中直接获取现有的对象,节省了频繁创建和销毁对象带来的资源浪费,这就是池的作用,为程序提供复用对象或者提前分配资源的能力。

ThreadPoolExecutor线程池介绍

下文仅针对线程池的一些要点做介绍

任务处理流程

核心线程池大小(corePoolSize)和最大线程池大小(maximumPoolSize)

ThreadPoolExecutor会根据corePoolSize(保持存活(不允许超时退出等)的最小工作线程数,如果设置了allowCoreThreadTimeOuttrue,则该值为0。可通过getPoolSize方法获取该值) 和maximumPoolSize(线程池中允许的最大线程数,可通过getMaximumPoolSize获取该值)设置的界限自动调整线程池的大小。

当通过execute(Runnable) 方法提交新任务后,如果正在运行的线程的数量小于corePoolSize,则创建新线程来处理请求,即使存在其它空闲的工作线程,否则如果正在运行的线程的数量大于corePoolSize,但小于maximumPoolSize,则仅仅在队列已经满时才会创建新线程来处理请求。设置corePoolSize等于maximumPoolSize则表示创建一个固定大小的线程池。

通过设置maximumPoolSize为基本无界的值,例如Integer.MAX_VALUE,则允许线程池容纳任意并发任务数。大多数情况下,corePoolSizemaximumPoolSize仅在构建时设置,但也可以分别用使用setCorePoolSizesetMaximumPoolSize对其进行动态更改。

按需创建线程

默认情况下,仅在新任务到达时创建和启动线程,即便是核心线程。可以使用prestartCoreThread或者prestartAllCoreThreads对此进行动态更改。如果使用非空队列构造线程池,你可能会想预先启动线程。

创建新线程

使用ThreadFactory创建新线程。如果未指定,则使用Executors.defaultThreadFactory,其创建的线程都位于相同线程组,且拥有相同的优先级NORM_PRIORITY以及非守护状态。通过提供不同的线程工程ThreadFactory,可以修改线程的名称,线程组,优先级,守护状态等等。当newThread返回null时,ThreadFactory将无法创建线程,此时执行器继续运行,但是可能无法执行任何任务。线程应该拥有modifyThread RuntimePermission。如果工作线程或者其它线程使用不具有该权限的线程池,服务可能被降级:配置变更可能不会及时生效,且关闭线程池可能会保留终止但未完成的状态。

线程保持存活时间

如果线程池当前拥有多于corePoolSize数量的线程,则空闲时间超过keepAliveTime(可通过getKeepAliveTime(TimeUnit)方法获取)的线程将被终止,以减少资源消耗。可以通过setKeepAliveTime(long,TimeUnit)方法动态改变该参数值。使用setKeepAliveTime(Long.MAX_VALUE, NANOSECONDS)可以有效的阻止空闲线程在关闭之前终止。默认情况下,keep-alive策略仅在线程池中线程数多余corePoolSize时起作用。keepAliveTime的值不为0的情况下,可通过allowCoreThreadTimeOut(boolean)方法将keep-alive策略应用于核心线程。

排队(Queuing)

BlockingQueue用于传输和容纳提交的任务。此队列的使用与线程池大小变化相关:

  • 如果线程池中当前线程数少于corePoolSize,那么Executor总是优先创建新线程来处理任务请求,而不是让任务请求排队
  • 如果线程池中当前线程数等于或者多余corePoolSize,那么Executor总是优先让任务排队,而不是创建新线程
  • 如果无法让任务请求排队(比如任务队列已满),且线程池中当前线程数未超过maximumPoolSize,则创建一个新线程来处理任务请求,否则将拒绝该任务请求

三种排队策略:

  • 直接传递(Direct handoffs)

    SynchronousQueue是工作队列(workQueue)的一个默认好选择。它将任务交给线程,而不是保留它们。此时,如果没有立即可用的线程,将构造新线程,因为让任务排队的尝试将会失败。此策略在处理可能具有内部依赖关系的请求集时避免锁定。通常需要无界的maximumPoolSize,以避免拒绝新任务的提交。这反过来说明当任务平均提交速度持续大于平均处理速度时,线程数无限增长的可能性。如果使用newCachedThreadPool创建线程池则表示使用直接传递策略

  • 无界队列(Unbounded queues)

    当所有核心线程都繁忙时,使用无界队列(例如,没有预定义容量的LinkedBlockingQueue)将导致新任务在队列中等待,从而导致没有多余corePoolSize的线程被创建(maximumPoolSize的值不起任何作用)。当每个任务完全彼此独立,互不影响执行时,这可能是合适的。例如,在网页服务器中, 这种排队方式用于平滑瞬时大量请求时很有用。需要注意的是,当任务平均提交速度持续大于平均处理速度时,可能会导致无界队列无限增长。如果使用newFixedThreadPool 创建线程池则表示使用无界队列。

  • 有界队列(Bounded queues)

    有界队列(例如,ArrayBlockingQueue)配合maximumPoolSizes使用有助于防止资源耗尽,但是难以调整和控制。队列大小和最大线程池大小需要相互权衡:使用大队列和较小的线程池可以最大限度地减少CPU使用率,操作系统资源和上下文切换开销,但是会导致人为的低吞吐量。如果任务频繁被阻塞(比如I/O限制),那么系统可以调度比我们允许的更多的线程。使用小队列通常需要较大的线程池,这会让CPU保持繁忙,但可能会产生不可接受的调度开销,这也会降低吞吐量。

拒绝处理任务

Executor已关闭、使用有界的线程池、工作队列,且达到最大值时,通过方法execute(Runnable)提交的任务将被拒绝。在任何一种情况下,execute方法调用其RejectedExecutionHandlerrejectedExecution(Runnable,ThreadPoolExecutor)方法。提供以下4种预定义处理策略:
ThreadPoolExecutor.AbortPolicy(默认策略)
拒绝任务时,处理器会抛出一个运行时异常RejectedExecutionException

ThreadPoolExecutor.CallerRunsPolicy
调用execute的线程自己运行任务。这提供了一个简单的反馈控制机制,将会降低新任务提交的速率。

ThreadPoolExecutor.DiscardPolicy
不能被执行的任务将被抛弃

ThreadPoolExecutor.DiscardOldestPolicy
如果Executor已关闭,工作队列队首的任务被丢弃,然后重试执行。(重试也可能失败,导致重复执行前面的动作)

可以定义和使用其他类型的RejectedExecutionHandler类。这样做需要一些谨慎,特别是当策略被设计为仅在特定容量或者队列策略下有效时

线程运行状态

该线程池使用了一个runState来对线程进行主要生命周期控制,具有以下值:

RUNNING: 接收新任务并且处理排队的任务
SHUTDOWN: 不接收新任务,但是处理排队的任务。
STOP: 不接收新任务,不处理排队的任务,并且中断正在进行的任务。
TIDYING: 所有任务已终止。workerCount为0。线程转为TIDYING状态将会运行terminated() hook方法。
TERMINATEDterminated()已经运行完。

这些值之间的数字顺序很重要,为了支持有序比较,runState会随着时间单调递增,但不需要达到每个状态。

状态转换如下:
RUNNING -> SHUTDOWN
调用shutdown()时,可能隐式的在finalize()中调用

RUNNING 或者 SHUTDOWN -> STOP
调用shutdownNow()

SHUTDOWN -> TIDYING
当工作队列和线程池都为空时
STOP -> TIDYING
线程池为空时

TIDYING -> TERMINATED
terminated() hook方法运行完成时。

线程的析构(Finalization)

如果线程池不再被程序引用且没有剩余的线程,线程池将被关闭。如果希望确保未被引用的线程池被回收,即使用户用户忘记调用shutdown,则必须通过适当的keep-alive配置,使用更低的下限--0核心线程数或者设置allowCoreThreadTimeOut(boolean),确保未使用的线程最终会消亡。

 

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

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

相关文章

哪本计算机书籍,让你有了醍醐灌顶突然开悟的感觉?

计算机书籍每年都会出版很多,但是能影响几代程序员的有这几本书,推荐一下,肯定让你有醍醐灌顶的开悟的感觉。 1、重构 改善既有代码的设计(第2版 平装版) 豆瓣评分:9.2 本书是一本为专业程序员编写的重构指…

30岁本科男,在测试行业干了五年还只会功能测试,难道真的要去送外卖吗?

在网上看到一个帖子 从发帖内容可以看出,题主是一位拥有五年功能测试经验的IT从业者,他也深知功能测试现在的处境艰难,想改变,却又因为年龄和经济压力的原因迟迟不敢迈出第一步,其实这是很多年近30岁的人事业危机的缩影…

【Java】之File类

个人主页:天寒雨落的博客_CSDN博客-C,CSDN竞赛,python领域博主 特别标注:仅为自己的学习记录笔记,方便复习和加深记忆,仅供借鉴参考! 前篇回顾:【java】之File类_天寒雨落的博客-CSDN博客 目录 目录的遍历…

五 系统安全分析与设计

目录 一、安全基础技术 1.1 对称与非对称加密 1.2 数字签名(防抵赖) 1.3 信息摘要(防篡改) 1.4 加密、数字签名、信息摘要结合使用 1.5 数字证书(防止公钥被截取篡改) 二、网络安全 2.1 安全协议 …

Android LayerDrawable 使用

1. 前言 Android LayerDrawble 包含一个Drawable数组&#xff0c;系统将会按照这些Drawable对象的数组顺序来绘制他们&#xff0c;索引最大的 Drawable 对象将会被绘制在最上面。 LayerDrawable对象的xml文件的根元素是<layer-list>&#xff0c; 该元素内部包含多个<i…

【Linux】开发工具之gdb调试器

目录&#x1f308;前言&#x1f337;1、debug与release&#x1f338;2、gdb选项&#x1f308;前言 本篇文章进行调试器gdb的学习&#xff01;&#xff01;&#xff01; &#x1f337;1、debug与release debug会生成需要调试的信息&#xff0c;release不会生成 程序的发布方式有…

【Node.js】模块的加载机制

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 目录 模块的加载机制 优先从缓存中加载 内置模块的加载机制 自定义模块的加载机制 第三方模块的加载机…

2022年浙江省中职组“网络空间安全”赛项模块B--Linux系统渗透提权

2022年中职组浙江省“网络空间安全”赛项 B-3:Linux系统渗透提权一、竞赛时间 420分钟 共计7小时 吃饭一小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第①阶段: 单兵模式系统渗透测试 任务一: Windows操作系统渗透测试 任务二: Linux操作系统渗透测试 任务…

经典算法|水仙花数|自幂数

算法题目 水仙花数&#xff08;Narcissistic number&#xff09;也被称为超完全数字不变数&#xff08;pluperfect digital invariant, PPDI&#xff09;、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数&#xff08;Armstrong number&#xff09;&#xff0c;水仙花数是指一个 3 位…

高精度RC振荡器的设计

1. 一些技术指标 应用于数字模拟混合信号芯片的高频率精度&#xff0c;高频率稳定度&#xff0c;全集成RC振荡器设计 由于数字电路指标仅与复杂度有关&#xff0c;此仅考虑模拟电路的设计指标。 项目Value电源电压2.5V~5.5V工作温度-40~125目标频率Ftyp 2MHZ频率精度&#x…

常用流媒体服务器

1、Mediasoup mediasoup是相对比较新的一个WebRTC服务器端的开源项目。它更多是通过集成包方式和其他应用服务器来集成。它支持SFU模式&#xff0c;主要支持视频聊天&#xff0c;媒体流广播等。 其特点是&#xff1a; 通过底层API实现和第三方集成&#xff0c;安装简单&#…

java自学第一天

1.1.体系&#xff1a; JavaSE&#xff08;J2SE&#xff09;&#xff08;Java2 Platform Standard Edition&#xff0c;java平台标准版&#xff09; JavaEE(J2EE)(Java 2 Platform,Enterprise Edition&#xff0c;java平台企业版) JavaME(J2ME)(Java 2 Platform Micro Edition&a…

如何提高代码交付效率,完成代码交付应用自动化?

为了提高代码交付效率&#xff0c;完成代码交付应用自动化&#xff0c;CoCode旗下Co-Project V2.5.0智能项目管理平台全新发布&#xff0c;新增CI/CD功能&#xff1a;Co-DevOps。 Co-DevOps是 CoCode 全新开发出的一项CI/CD功能&#xff0c;提供持续集成、持续交付&#xff08;…

六轴工业机器人

连杆原理 符号中文名含义aaa连杆长度两个相邻关节轴之间的公垂线的长度α\alphaα连杆转角两个相邻关节间轴之间形成的角度&#xff0c;右手定则前一个轴到后一个轴ddd连杆偏距两个相邻连杆之间的距离&#xff0c;高度差θ\thetaθ关节角两个相邻杆绕公共关节轴旋转的角度 机…

TypeScript-01基础知识

目录 一、ts与es、js之间的关系 二、TypeScript与JavaScript之间的区别 三、安装TypeScript编译器 四、执行typescript的步骤 五、ts的数据类型 1、类型别名 2、接口 接口 与 类型别名 的区别 3、类型断言 4、文字类型 不常用枚举、bigint、symbol 六、类型缩小 1、…

Linux socket 编程 UDP

套接字&#xff1a;操作系统向上层提供的用于实现网络通信的统称 网络通信其实本质上就是两台主机之间的通信其中一段是客户端&#xff0c;另一端是服务器 客户端&#xff1a;用户的一端&#xff0c;客户端是主动发出请求的一端 服务端&#xff1a;针对用户请求提供服务的一端…

Linux 进程控制

&#x1f9d1;‍&#x1f4bb;进程控制 &#x1f9d1;‍&#x1f4bb; 文章目录&#x1f9d1;‍&#x1f4bb;进程控制 &#x1f9d1;‍&#x1f4bb;一、进程创建1. fork函数2. fork常规用法3. fork创建子进程操作系统都做了什么&#xff1f;4. 写时拷贝5. 父子进程代码的共享…

String(二)————迭代器及相关接口使用

目录 string构造接口&#xff08;Construct string object&#xff09; string的元素访问&#xff08;读写&#xff09; 迭代器 string构造接口&#xff08;Construct string object&#xff09; string相比于C语言的字符数组要好用的多&#xff0c;无论是在初始化还是在读写…

排序5:直接选择排序

目录 排序思想&#xff1a; 演示图&#xff1a; 代码实现 总结&#xff1a; 排序思想&#xff1a; 在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素 若它不是这组元素中的最后一个(第一个)元素&#xff0c;则将它与这组元素中的最后一个&#xff08;第一个…

SpringMVC---->自我实现底层机制(吃透springMVC)

目录 配套代码在资源中&#xff08;免费&#xff09; maven环境搭配 注解注入的规范&#xff1a; 一.开发HongDisptcherServlet前端控制器 1.说明&#xff1a; 2.配置web.xml文件 3.检查前期工作是否成功 二.完成客户端/浏览器请求控制层 1.创建 自己的 Controller 和…