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个,当还有任务进行添加时,此时将采取饱和策略。
热门推荐
芒果干的美味与潜在上火问题:科普与建议
中国足球走出低谷!U20国足强势取胜,9数据领先,媒体人狂赞
日用品的分类标准是什么?
智慧校园:科技引领教育未来,创新赋能学习体验
手机钢化膜应该怎么贴?看完涨知识了!建议收藏
燥与湿八字分析:燥土与湿土的命理差异
浅谈中国耳饰的发展
脚后跟不红不肿,一走路就疼,是啥原因?5大“元凶”需警惕
“武汉和平鸟”玩偶“蒜鸟”走红,一天能卖1000多个
对友情的失望的句子
2025年最新公务员体检标准全解析:哪些情况不能合格?标准有无放宽?
现代啤酒酿造用糖:种类、用途与酿造技巧详解
吃货必备!烤生蚝的正确打开方式
AI绘画提示词写作指南:明确性、详细性、层次性与创造性
北证屡创新高之际,我们给北交所公司做了个画像
医生提醒:6个无意识的小习惯, 正在悄悄伤害你的鼻子
“有之以为利,无之以为用”的深入解读
西餐厅如何通过灯光设计提升用餐氛围?
贵州10大特色小吃惹人爱,羊肉粉名声遐迩,豆花面辣中带香
王者荣耀S37赛季新英雄"影"攻略:技能解析与实战技巧详解
如何计算金木水火土五行
一建水利实务老师推荐及报考分析
如何从狗狗的行为判断其心情(狗狗的心情信号)
学编程对孩子有什么好处?坦白讲!
月子中心 省心还是闹心?
后突厥的杰出领袖:默啜可汗的生平与成就
解决投影仪分辨率差的有效方法(提升投影仪画质的实用技巧)
耳环材质选择:金属与宝石的对比
明朝晚期的一位明君——朱翊钧
汽车油水分离器的作用及汽车油管维护指南