ThreadPoolExecutor 线程池理论、饱和策略、工作队列排队策略
创作时间:
作者:
@小白创作中心
ThreadPoolExecutor 线程池理论、饱和策略、工作队列排队策略
引用
CSDN
1.
https://blog.csdn.net/wangmx1993328/article/details/80582803
线程池是Java并发编程中的重要概念,它能够有效地管理和复用线程资源,提高程序的执行效率。本文将详细介绍Java中的ThreadPoolExecutor线程池,包括其基本概念、工作原理、饱和策略以及工作队列的排队策略。
ThreadPoolExecutor 线程池概述
- 线程池是JDK1.5开始引入的,也叫Executor框架,或是Java并发框架
- 线程池相关的API在java.util.concurrent包中,常用到以下几个类和接口:
- java.util.concurrent.Executor:一个只包含一个方法的接口,它的抽象含义是:用来执行一个Runnable任务的执行器
- java.util.concurrent.ExecutorService:继承了Executor接口的接口,增加了很多对于任务和执行器的生命周期进行管理的方法
- java.util.concurrent.ThreadFactory:一个生成新线程的接口。用户可以通过实现这个接口管理对线程池中生成线程的逻辑
- java.util.concurrent.Executors:创建并返回其余各个实例的类,提供了很多不同的生成执行器的实用方法,比如基于线程池的执行器的实现。
- java.util.concurrent.ThreadPoolExecutor:这个类维护了一个线程池,对于提交到此Executor中的任务,它不是创建新的线程而是使用池内的线程进行执行,对于数量巨大但执行时间很短的任务,可以显著地减少对于任务执行的开销。
Executor 框架结构
- executor 结构主要包括任务、任务的执行和异步结果的计算。
- 任务:包括被执行任务需要实现的接口,如Runnable接口或Callable接口
- 任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)
- 异步计算的结果:包括接口Future和实现Future接口的FutureTask类
使用线程池的好处:
- 降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控
线程池工作原理
- 当一个新的任务提交到线程池之后,线程池处理过程如下:
- 线程池判断核心线程池里的线程是否已满。未满时,则创建一个新的工作线程来执行任务。如果核心线程池里的线程已满,则执行第二步。
- 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里等待执行。如果工作队列满了,则执行第三步。
- 线程池判断线程池(核心线程池外的线程池部分)的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
线程池饱和策略
- 常用的饱和策略如下(它们是 ThreadPoolExecutor 类中的内部类,可以直接调用):
Modifier and Type | Class and Description |
|---|---|
static class | ThreadPoolExecutor.AbortPolicy 被拒绝的任务的处理程序,抛出一个RejectedExecutionException。 |
static class | ThreadPoolExecutor.CallerRunsPolicy 一个被拒绝的任务的处理程序,直接在execute方法的调用线程中运行被拒绝的任务,除非执行程序已经被关闭,否则这个任务被丢弃。 |
static class | ThreadPoolExecutor.DiscardOldestPolicy 被拒绝的任务的处理程序,丢弃最旧的未处理请求,然后重试execute,除非执行程序关闭,在这种情况下,任务被丢弃。 |
static class | ThreadPoolExecutor.DiscardPolicy 被拒绝的任务的处理程序静默地丢弃被拒绝的任务。 |
- AbortPolicy:Java 线程池默认的阻塞策略,即不执行此新任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute 需要 try catch,否则程序会直接退出。
- DiscardPolicy:直接抛弃,新任务不执行,空方法
- DiscardOldestPolicy:从队列里面抛弃head的一个任务,并再次execute 此task。
用户自定义拒绝策略(最常用):实现RejectedExecutionHandler,并自己定义策略模式
线程池工作流程图
- 以 ThreadPoolExecutor 为例展示线程池的工作流程
- 如果当前运行的线程少于corePoolSize(核心线程数),则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
- 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue(阻塞队列/任务队列)。
- 如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
- 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并执行线程饱和策略,如:RejectedExecutionHandler.rejectedExecution()方法。
- ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。
工作队列排队策略
- 已经说过当线程池中工作线程的总数量超过核心线程数量后,新加的任务就会放入工作队列中进行等待被执行
- 使用线程池就得创建ThreadPoolExecutor对象,通过ThreadPoolExecutor(线程池)类的构造方法创建时,就得指定工作队列,它是BlockingQueue
接口,而实际开发中是指定此接口的具体实现类,常用的如下所示。
SynchronousQueue 直接提交
- 直接提交策略----意思是工作队列不保存任何任务被等待执行,而是直接提交给线程进行执行。
- 工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保存它们。
- 如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。
- 此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。
- Executors的newCacheThreadPool()方法创建线程池,就是使用的此种排队策略
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
LinkedBlockingQueue 无界队列
- 无界队列策略----无界指的是工作队列大小没有上限,可以添加无数个任务进行等待。
- 使用无界队列将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。于是创建的线程就不会超过 corePoolSize。因此,maximumPoolSize 的值也就无效了。所以一般让corePoolSize等于maximumPoolSize
- 当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列
- Executors的newFixedThreadPool(int nThreads)方法创建线程池,就是使用的此种排队策略
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ArrayBlockingQueue 有界队列
- 有界队列策略----意思是工作队列的大小是有限制的
- 优点是可以防止资源耗尽的情况发生,因为如果工作队列被无休止的添加任务也是很危险的
- 当工作队列排满后,就会执行线程饱和策略
// 构造线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 4,
3, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardOldestPolicy());
- 如上核心线程为3个,每个线程的工作队列大小为2(即队列中最多有两个任务在等待执行),线程池最大线程数为4个
- 所以当工作线程数小于等于3时,直接新建线程执行任务;超过3时,任务会被添加进工作队列进行等待,3*2=6,当工作队列等待的任务数超过6个以后,则又会新建一个线程,此时整个线程池线程总数已经达到了4个,当还有任务进行添加时,此时将采取饱和策略。
热门推荐
鸡的象征意义和精神
TN-C系统与TN-S系统详解:供配电系统的两种主要类型
“00后”涌入佛山工厂,要高薪,也要体面
餐边柜选购全攻略:从选材到布局,一文掌握核心要点
5种餐边柜设计拯救小餐厅!收纳扩容×颜值翻倍攻略
青岛郊区自驾游烧烤攻略
咖啡的种类及制作原理
南京市妇幼保健院丁家庄院区正式启用 就医感受焕然“医”新
如何用特效和视觉效果为电影增加独特魅力
电锯:类型、品牌、使用方法与安全指南
北京买房退税的办理流程,北京买房退税的办理流程及时间
八字命理学习指南:从入门到精通的五个步骤
彭光谦:伟人母亲文素勤系民族英雄文天祥后裔
感冒是什么病毒
结膜炎和角膜炎的区别是什么?
美元走势仍是影响人民币汇率的主要因素
南京南站内换乘怎么换
神舟十九号成功发射,美国人如今可曾懊悔:当初不该放钱学森回国?
CPL门可以打发泡胶固定吗?专业分析与建议
12张跳芭蕾的小女生涂色图片免费下载!附详细绘画教程
“海上精灵”频现三亚,背后的生态“答案”是……
高效LED照明解决方案提升道路行车安全
路灯智能控制器到底能为道路照明做些什么?
降压药应该啥时候吃?早晨、中午还是晚上?
更年期不用干预?这是个误会 | 世界更年期关怀日
一文读懂:宝宝口腔溃疡、手足口病、鹅口疮如何区分?
企业拖欠劳动者工资的利息怎么计算
中式婚礼喜娘的任务 中式婚礼喜娘是干嘛的
集成光伏的含义、工作原理和应用领域
巫师3终极攻略:阿尔德刺骨技能深度解析,伤害爆表与战术效果全!