网站首页 > 技术文章 正文
1 线程池快速回顾
《Java 并发编程的艺术》中提到了使用线程池的好处,概括起来如下:
- 降低资源损耗。通过重复利用已创建的线程降低线程创建和销毁的损耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。使用线程池可以进行统一的分配,调优和监控。
Java里使用线程池,主要就是用的ThreadPoolExecutor类,先来看一下 ThreadPoolExecutor 类中的构造方法:
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
复制代码
ThreadPoolExecutor 中最重要的参数:
- corePoolSize:核心线程数。最小可以同时运行的线程数。
- maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的最大线程数。
- workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到corePoolSize,如果达到的话,新任务就会被存放在队列中。如果workQueue已经满了的话就执行拒绝策略。
ThreadPoolExecutor 的其他参数:
- keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被销毁。
- unit : keepAliveTime 参数的时间单位。
- threadFactory:executor 创建新线程的时候会用到。
- handler:拒绝策略。
当参数设置完毕后,线程池的工作原理具体是什么呢?我们可以通过下面这个面试题来理解一下:
假设我们设置的线程池参数为:corePoolSize=10, maximumPoolSize=20,queueSize = 10 20个并发任务过来,有多少个活跃线程?
10个。corePoolSize打满,queueSize 也满
21个并发任务过来,有多少个活跃线程?
11个。corePoolSize打满,queueSize 也满还多一个,maximumPoolSize = 20,所以corePoolSize + 1此时活跃的为11个。
30个并发任务过来,有多少个活跃线程?
20个。corePoolSize打满,queueSize 也满,corePoolSize扩充至20,此时有20个活跃任务。
31个并发任务过来,有多少个活跃线程?
20个。corePoolSize打满,queueSize 也满,corePoolSize扩充至20还多一个,如果是丢弃策略,此时有20个活跃任务。
上面的流程可以总结成如下所示的流程图:(来源于tech.meituan.com/2020/04/02/…)
2 现有设置参数的方法及不足
回顾完线程池的核心技术点之后就要开始思考本文主要讨论的内容了:线程池参数应该如何设置?
如果你把这个问题输入到浏览器里,极大可能是下面这种答案:
上面的理论看似很华丽,但现实却是很残酷的。。。你会发现虽然按照上面的指导思想进行配置了,但效果并不能让人满意,造成这种后果的原因有很多,包括但不仅限于:
- 任务到底是CPU还是IO密集的特征不明显
- 同一个机器上可能部署不止一个服务,不同服务之间也会抢占资源
针对上述问题,美团给出的对应的解决方案就是——线程池参数动态化
那么如何实现参数动态化呢?
接触过微服务开发的同学们可能就会想到了,我们完全可以借助一个配置中心来做,这样就能够实现线程池参数的动态配置和即时生效(在阿里内部也有一个专门的中间件,diamond),省去了重新部署程序并发布的步骤,通常在企业里这一系列流程下来还是比较费时间的。
(来源于tech.meituan.com/2020/04/02/…)
3 如何设置核心线程数(corePoolSize)
其实 ThreadPoolExecutor 类库里直接就有这个方法:
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize)
interruptIdleWorkers();
else if (delta > 0) {
// We don't really know how many new threads are "needed".
// As a heuristic, prestart enough new workers (up to new
// core size) to handle the current number of tasks in
// queue, but stop if queue becomes empty while doing so.
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
复制代码
我们直接看英文注释,这就是作者直接想要表达的意思。大致翻译一下:
设置线程的核心数量,如果新的corePoolSize值小于当前corePoolSize值,多出来的线程将在其下次空闲时被终止。如果新的corePoolSize值大于当前corePoolSize值,就可以创建新的worker来执行队列里的任务
(来源于tech.meituan.com/2020/04/02/…)
4 如何设置最大线程数(maxPoolSize)
同样地 ThreadPoolExecutor 类库里也有这个方法:
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
this.maximumPoolSize = maximumPoolSize;
if (workerCountOf(ctl.get()) > maximumPoolSize)
interruptIdleWorkers();
}
复制代码
这个方法的注释和上面的方法类似,大家可以对照着看:
逻辑也并不复杂:
- 参数校验
- 设置最大线程数 maxPoolSize
- 如果工作线程数是否大于最大线程数,则对空闲线程发起中断
JDK原生线程池ThreadPoolExecutor还提供了其他设置参数的方法:
5 如何改变等待队列长度
等待队列的长度capacity被final修饰符修饰,所以按理说是不能修改的
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
复制代码
唯一可能的办法就是自己定义一个队列,在美团的实现里就是一个名为ResizableCapacityLinkedBlockIngQueue的队列,根据名称也不难看出,这个队列的容量是可变的。
具体的实现细节美团好像并没有公布出来,不过我们可以简单的将原先 LinkedBlockingQueue的capacity的final修饰符去掉,并提供getter和setter方法,形成我们自己的ResizableCapacityLinkedBlockIngQueue
猜你喜欢
- 2024-10-22 线程池调优之动态参数配置 动态设置线程池大小
- 2024-10-22 java线程池参数及使用 java线程池的用法
- 2024-10-22 面试官:说说你对线程池的了解 线程池实现原理面试
- 2024-10-22 「每日分享」高阶程序员需要掌握的常见性能优化策略
- 2024-10-22 池化技术学习 池化方法
- 2024-10-22 「Java基础」「多线程」-线程池 java多个线程池
- 2024-10-22 线程池配置的常见误区 线程池配置参数有哪些
- 2024-10-22 创建线程池参数有哪些作用? 创建线程池的7个参数
- 2024-10-22 一文搞懂!多线程之间的通信及线程池
- 2024-10-22 码农大叔带你——解析线程池 线程池的主要处理流程
你 发表评论:
欢迎- 最近发表
-
- 吴谨言专访大反转!痛批耍大牌后竟翻红,六公主七连发力显真诚
- 港股2月28日物业股涨幅榜:CHINAOVSPPT涨1.72%位居首位
- 港股2月28日物业股午盘:CHINAOVSPPT涨1.72%位居首位
- 港股3月2日物业股涨幅榜:CHINAOVSPPT涨1.03%位居首位
- 港股3月2日物业股午盘:CHINAOVSPPT涨1.03%
- 天赋与心痛的背后:邓鸣贺成长悲剧引发的深刻反思
- 冯小刚女儿徐朵追星范丞丞 同框合照曝光惹人羡,回应网友尽显亲民
- “资本大佬”王冉:51岁娶小17岁童瑶,并承诺余生为娇妻保驾护航
- 港股3月2日物业股午盘:CHINAOVSPPT涨1.03%位居首位
- 「IT之家开箱」vivo S15 图赏:双镜云窗,盛夏风光
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)