线程池详细解析(一)

article/2025/6/24 16:27:49

java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理的使用线程池能够带来三个好处。

1. 降低资源消耗:频繁的创建线程和销毁线程会产生不少的资源上的消耗,通过重复利用已创建的线程可以降低资源消耗。

2.提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

3.提高线程的可管理性:通过线程池来对所有创建的线程进行资源的管理,对其可以进行统一的资源分配,调优和监控等功能,从而提高了稳定性

实现原理:

当任务向线程池提交一个任务之后,线程池是如何处理这个任务的呢?

下面我们看一个示意图:

上图我们可以分析出

1.当提交任务的时候,线程池会先判断当前核心线程是否已满(占用完),如果未占满则直接使用核心线程

2.当核心线程占满的时候就会将其放入队列中进行等待,放入之前也会判断队列是否已满,未满则放入队列

3.如果已经满了,线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的线程(这里的新线程是一个临时线程如果线程使用完毕之后则会回收这个线程)。如果都满了则会交给饱和策略来处理这个任务

下面我们看看线程池的几个关键参数:

核心线程数 (corePoolSize)

  • 本质:线程池的常驻工作兵力

  • 行为特点

    • 即使线程空闲也不会销毁(除非设置 allowCoreThreadTimeOut=true)

    • 新任务优先使用核心线程执行

最大线程数 (maximumPoolSize)

  • 本质:线程池的最大兵力上限

  • 行为特点

    • 当核心线程满且队列满时,临时创建新线程(直到达到此值)

    • 空闲超时后销毁(由 keepAliveTime 控制)

线程存活时间 (keepAliveTime)

  • 本质非核心线程的空闲存活时间

  • 行为特点

    • 线程空闲超过此时间后自动销毁

    • 仅作用于超过 corePoolSize 的线程

  • 单位TimeUnit(秒/毫秒/纳秒)

任务队列 (workQueue)

  • 本质任务缓冲区的容器

  • 常见实现及适用场景

队列类型特性适用场景
SynchronousQueue零容量队列,直接传递任务高响应要求,拒绝任务转移
ArrayBlockingQueue有界固定容量队列流量可控的系统
LinkedBlockingQueue可选有界/无界队列(默认无界)吞吐量优先,允许任务堆积
PriorityBlockingQueue带优先级的队列任务有优先级区分时
  • 危险点
    ⚠️ 无界队列可能导致 OOM(堆积过多任务)
    ⚠️ 有界队列需配合合理的拒绝策略

拒绝策略 (RejectedExecutionHandler)

  • 触发条件:线程数达最大且队列满时

  • 四大内置策略

策略名行为适用场景
AbortPolicy(默认)抛出 RejectedExecutionException需要快速失败感知的系统
CallerRunsPolicy由提交任务的线程直接执行保证任务不丢失的慢速降级
DiscardPolicy静默丢弃想要加入的任务可容忍丢失的场景(如日志)
DiscardOldestPolicy丢弃队列头部的任务并重试新任务优先处理新任务的场景

源码分析:

接下来我们选几个线程池比较核心的方法进行一些源码分析

内部属性:

下面我们来看看没有介绍过的线程池内部的属性

    private final AtomicInteger ctl;private static final int COUNT_BITS = 29;private static final int CAPACITY = 536870911;private static final int RUNNING = -536870912;private static final int SHUTDOWN = 0;private static final int STOP = 536870912;private static final int TIDYING = 1073741824;private static final int TERMINATED = 1610612736;private final BlockingQueue<Runnable> workQueue;private final ReentrantLock mainLock;private final HashSet<Worker> workers;private final Condition termination;

ctl表示两个状态值--线程池状态以及工作线程最大数量(线程池总线程数)

COUNT_BITS 表示在Integer的几位表示线程数

而后续的几个int变量值都是表示的是线程池状态值

workQueue则是线程池内部的任务队列用来存放任务

mainLock线程池内部的锁用来保证同步

termination目的是当线程池进行关闭的时候会将调用关闭方法的线程放入到Condition队列中进行等待,当线程池完全关闭之后唤醒该线程

核心方法execute

下面我们看看核心方法execute,揭秘线程池是如何运行任务的:

    public void execute(Runnable var1) {if (var1 == null) {throw new NullPointerException();} else {int var2 = this.ctl.get();if (workerCountOf(var2) < this.corePoolSize) {if (this.addWorker(var1, true)) {return;}var2 = this.ctl.get();}if (isRunning(var2) && this.workQueue.offer(var1)) {int var3 = this.ctl.get();if (!isRunning(var3) && this.remove(var1)) {this.reject(var1);} else if (workerCountOf(var3) == 0) {this.addWorker((Runnable)null, false);}} else if (!this.addWorker(var1, false)) {this.reject(var1);}}}

首先对任务进行判空处理,随后获取线程池的状态变量进行判断当前线程数是否小于核心线程数,如果小于核心线程数则直接调用addWorker创建核心线程来执行当前任务

下面我们看看addWorker方法是如何创建核心线程来执行任务的

    private boolean addWorker(Runnable var1, boolean var2) {while(true) {int var3 = this.ctl.get();int var4 = runStateOf(var3);if (var4 >= 0 && (var4 != 0 || var1 != null || this.workQueue.isEmpty())) {return false;}while(true) {int var5 = workerCountOf(var3);if (var5 >= 536870911 || var5 >= (var2 ? this.corePoolSize : this.maximumPoolSize)) {return false;}if (this.compareAndIncrementWorkerCount(var3)) {boolean var18 = false;boolean var19 = false;Worker var20 = null;try {var20 = new Worker(var1);Thread var6 = var20.thread;if (var6 != null) {ReentrantLock var7 = this.mainLock;var7.lock();try {int var8 = runStateOf(this.ctl.get());if (var8 < 0 || var8 == 0 && var1 == null) {if (var6.isAlive()) {throw new IllegalThreadStateException();}this.workers.add(var20);int var9 = this.workers.size();if (var9 > this.largestPoolSize) {this.largestPoolSize = var9;}var19 = true;}} finally {var7.unlock();}if (var19) {var6.start();var18 = true;}}} finally {if (!var18) {this.addWorkerFailed(var20);}}return var18;}var3 = this.ctl.get();if (runStateOf(var3) != var4) {break;}}}}

首先说明一下参数:第一个参数表示任务,第二个参数Boolean类型则是表示启用何种线程进行执行true表示核心线程,false表示临时线程。

首先进入方法中就是两个while嵌套循环,第一个循环则是判断当前线程池的状态是否可以继续创建线程执行任务否则返回false,第二个则是首先判断当前线程数是否可以继续创建线程否则返回false,随后开始使用CAS来修改状态值增加1个线程数。随后则创建任务线程,在线程池中任务线程则是一个Worker对象,创建完成之后将其加入到线程池的线程集合中,然后更改线程集合的大小,这里的操作都是采用lock全局锁的机制进行创建的保证了线程同步,上述都完成之后则开始调用线程的start方法正式运行线程并且修改标志位,如果出现了异常则会调用addWorkerFailed进行回滚的操作,这里的回滚操作则是线程数减一,状态位更改等操作。

随后我们接着看execute之后的代码逻辑

 if (isRunning(var2) && this.workQueue.offer(var1)) {int var3 = this.ctl.get();if (!isRunning(var3) && this.remove(var1)) {this.reject(var1);} else if (workerCountOf(var3) == 0) {this.addWorker((Runnable)null, false);}} else if (!this.addWorker(var1, false)) {this.reject(var1);}

这里如果当前核心线程数已经占满那么就会将其丢入阻塞队列中也就是任务队列,如果成功之后则会再次检查当前线程池的状态标志位是否可以执行任务,如果不可以则将任务从中移除,并且执行拒绝策略,如果当前线程数为0则创建一个临时线程去执行(自定义线程池如果核心线程为0则会出现这些情况)。如果队列已经满了则会创建临时线程去执行,如果也不行就会执行拒绝策略。

Worker类:

我们来看一看Worker对象的内部构造

    private final class Worker extends AbstractQueuedSynchronizer implements Runnable {private static final long serialVersionUID = 6138294804551838833L;final Thread thread;Runnable firstTask;volatile long completedTasks;Worker(Runnable var2) {this.setState(-1);this.firstTask = var2;this.thread = ThreadPoolExecutor.this.getThreadFactory().newThread(this);}public void run() {ThreadPoolExecutor.this.runWorker(this);}protected boolean isHeldExclusively() {return this.getState() != 0;}protected boolean tryAcquire(int var1) {if (this.compareAndSetState(0, 1)) {this.setExclusiveOwnerThread(Thread.currentThread());return true;} else {return false;}}。。。。。}

可以看出它是存在线程池内部的内部类,封装了线程池中的工作线程,并通过继承 AbstractQueuedSynchronizer(AQS)实现了独特的锁机制,封装工作线程和任务我们可以理解但为什么内部还要实现AQS的锁机制呢?

首先我们可以看到当Worker进行初始化的时候将state设置为-1

  Worker(Runnable var2) {this.setState(-1);this.firstTask = var2;this.thread = ThreadPoolExecutor.this.getThreadFactory().newThread(this);}

这里的-1就是防止线程在初始化过程中被中断(state=-1)。通过AQS的 state 值(-101)区分线程的不同阶段(未启动、空闲、忙碌)。线程池的shutdown方法会遍历所有的线程根据AQS的state不同进行对应的中断操作。换句话来说worker内部采用AQS的更多目的是通过AQS的state来表示当前Worker的一个状态,随后根据这些状态来执行不同的业务逻辑处理,而不是真正的去使用锁。在后续文章中将会源码分析shutdown方法来看看是如何中断线程的。

因此Worker 集成锁机制的核心目的是:

  1. 启动保护:防止线程在初始化过程中被中断(state=-1)。
  2. 状态区分:通过 state 值(-101)区分线程的不同阶段(未启动、空闲、忙碌)。
  3. 精确中断:配合 shutdown() 和 shutdownNow() 实现不同的中断策略,既保证任务完整性,又能快速响应关闭请求。

总结execute方法:

到此execute方法已经解析完毕,可以看上述流程图对整个方法的业务逻辑有一个清晰的认知。接下来我们将会讲一讲线程池的其他方法。

线程池实际上使用了两种锁:一个是全局锁lock还有一个是每个Worker线程内部自带的AQS实现锁,全局锁的目的更主要是为了线程同步,而AQS锁的目的则更主要的是为了实现Worker 的状态控制。


http://www.hkcw.cn/article/MwBSbARjKC.shtml

相关文章

韩国大选今日开始投票 五候选人竞逐总统

韩国第21届总统大选于当地时间6月3日6时正式开始,全国共设有14295个投票站。未参加提前投票的选民凭身份证件前往指定投票站即可参与投票,投票将于当日20时结束。本次大选共有7位候选人登记,但其中两位宣布退出并支持国民力量党候选人金文洙。因此,选民将从以下五位候选人中…

米奇盖顿:音乐架起中美交流桥梁 纯粹情感共鸣

米奇盖顿在湖南卫视《歌手2025》的舞台上演唱完《If I Were a Boy》后,不禁潸然泪下。这位来自美国得克萨斯州的乡村音乐女歌手,曾在2021年获得格莱美最佳乡村独唱表演提名,成为首位提名该奖项的黑人女歌手。在中国观众毫无保留的情感反馈中,她感受到了一种久违的真诚。在美…

字节跳动社招面经 —— BSP驱动工程师(6)

接前一篇文章&#xff1a;字节跳动社招面经 —— BSP驱动工程师&#xff08;5&#xff09; 本文内容参考&#xff1a; 嵌入式硬件平台修改启动地址-CSDN博客 特此致谢&#xff01; 上一回开始针对于“嵌入式充电站”发的一篇文章字节跳动社招面经——BSP驱动工程师中的面试题…

检索器组件深入学习与使用技巧 VectorStoreRetriever 检索器

1. VectorStoreRetriever 检索器 VectorStoreRetriever 是 BaseRetriever 的子类&#xff0c;这是一个专门针对向量数据库的基础检索器&#xff0c;在 VectorStoreRetriever 的内部实现了 _get_relevant_documents() 方法&#xff0c;还定义了单独的属性&#xff1a; vectors…

深度学习pycharm debug

深度学习中&#xff0c;Debug 是定位并解决代码逻辑错误&#xff08;如张量维度不匹配&#xff09;、训练异常&#xff08;如 Loss 波动&#xff09;、数据问题&#xff08;如标签错误&#xff09;的关键手段&#xff0c;通过打印维度、可视化梯度等方法确保模型正常运行、优化…

女研究生二次入伍再续海军梦 逐梦军营续写辉煌

女研究生二次入伍再续海军梦!在2025年春季入伍的新兵中,有一名特殊的“老兵”。她曾在海军某部服役两年,退伍后重返校园继续学业。如今已是研究生的她再次穿上海军军装,来到南部战区海军某训练基地,又一次成为了海军战士,踏上了二次入伍的军旅生涯。张玉玺于2020年9月第一…

尾号7个0的手机号拍出61.2万元 独特号码高价成交

一个尾号为七个零的手机号码使用权于6月1日以25万元底价起拍,共有13人参与竞拍。最终该号码以61.2万元成交。公告显示,截至4月23日,该号码无欠费,余额约9.14元,已办理4G全国流量王8元套餐,未绑定宽带,过户无预存话费要求,完成过户即可转网。目前,该号码使用权已被天津…

中国单方面免签“朋友圈”扩大 拉美五国加入免签行列

中国单方面免签“朋友圈”扩大 拉美五国加入免签行列。从6月1日起,巴西、阿根廷、智利、秘鲁、乌拉圭五个国家的持普通护照人员可以享受免签政策。这一政策试行期为2025年6月1日至2026年5月31日,期间这些国家的公民来华经商、旅游观光、探亲访友、交流访问或过境不超过30天,…

贝尔伯克当选联大主席 大V解读 女性力量再添一笔

6月2日,德国前外长贝尔博克当选为第80届联合国大会主席,成为第五位担任此职位的女性。联合国大会以167票赞成通过了安娜莱娜贝尔博克出任下一任主席的决定。贝尔博克对此表示非常感激,并承诺将通过“基于信任的对话”为大家服务。她宣称自己的目标是减少冗余、提高透明度,并…

上海游客“表扬帖”走红网络 感谢信点赞厦门辅警

上海游客“表扬帖”走红网络 感谢信点赞厦门辅警!一篇饱含深情的表扬贴在小红书上迅速走红,网友查女士诚挚地感谢了一位帮助她的厦门辅警。厦门双子塔上灯光轮滚播放着连续七届获选文明城市的信息,这是每一位厦门人共同努力、团结一致的结果。5月25日中午11点多,上海游客查…

如何评估 RAG 的分块Chunking策略

如何评估 RAG 的分块策略 我对 RAG&#xff08;检索增强生成模型&#xff09;进行了深入研究&#xff0c;深知分块在任何 RAG 流水线中都至关重要。 我接触过的许多人坚信更好的模型能够提升 RAG 的性能。有些人则对向量数据库寄予厚望。即便那些认同分块重要性的人&#xff…

贪心算法应用:顶点覆盖问题详解

贪心算法应用&#xff1a;顶点覆盖问题详解 贪心算法是解决顶点覆盖问题的经典方法之一。下面我将从基础概念到高级优化&#xff0c;全面详细地讲解顶点覆盖问题及其贪心算法解决方案。 一、顶点覆盖问题基础 1. 问题定义 顶点覆盖问题&#xff08;Vertex Cover Problem&am…

代码随想录算法训练营第十一天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素、栈与队列总结

150. 逆波兰表达式求值--后缀表达式 力扣题目链接(opens new window) 根据 逆波兰表示法&#xff0c;求表达式的值。 有效的运算符包括 , - , * , / 。每个运算对象可以是整数&#xff0c;也可以是另一个逆波兰表达式。 说明&#xff1a; 整数除法只保留整数部分。 给…

女童玩的气球突然爆炸 静电引发惊魂一刻

一位江苏宝妈在六一儿童节前发布了一段视频,提醒家长们不要给孩子买氢气球玩。她描述了自己孩子遇到的一次危险情况:气球突然爆炸,孩子的后脑勺头发被烧焦了一层,手臂上的汗毛也被烧掉了。事发时,这位宝妈正在给孩子喂食鸡腿,突然听到“嘭”的一声,气球在孩子身后爆裂,…

Ubuntu安装CH340驱动教程

Ubuntu22.04安装CH340驱动 3.1 用lsusb查看USB 插上CH340之前 插上CH340之后 输出中包含ID 1a86:7523 QinHeng Electronics CH340 serial converter的信息&#xff0c;这表明CH340设备已经被系统识别。 3.2 查看USB转串口 ls -l /dev/ttyUSB0/dev下没有该设备节点。 用dme…

新人雨中骑“鸡动车”去结婚 创意婚礼引关注

6月1日,在合肥渡仙桥路上,一对新人以独特的方式前往婚礼现场。他们没有选择豪华婚车,甚至没有开车,而是骑着小鸡造型的两轮电动车赶往婚礼现场。这对新人表示,看到这种小鸡造型的电动车觉得很漂亮,便决定用它作为婚车。视频中,这支婚车车队虽然数量不多,但在路上格外显…

上海迪士尼打架事件后续 因拍照引发冲突

5月31日,有网友发布视频称,在上海迪士尼乐园内一对情侣和一家三口发生了冲突。视频中可以看到双方在现场扭打,周围游客纷纷上前劝阻。6月1日,当地相关部门透露,该事件发生在5月31日下午,在迪士尼疯狂动物城的一处拍照打卡点,双方因拍照问题发生争执,随后演变成肢体冲突…

【Python连接数据库基础 02】SQLAlchemy核心实战:SQL表达式构建与执行完全指南

【Python连接数据库基础 02】SQLAlchemy核心实战&#xff1a;SQL表达式构建与执行完全指南 关键词&#xff1a;SQLAlchemy Core、SQL表达式、数据库查询、Python ORM、表达式语言、数据库操作、查询构建、SQLAlchemy教程 摘要&#xff1a;本文深入讲解SQLAlchemy Core的核心功能…

台湾一大学生暗网贩毒被捕 涉案金额超7亿

美国联邦调查局近日破获了暗网毒品交易平台“隐身市场”,该平台的经营者“法老”被发现是24岁的台湾大学资管系学生林睿庠。林睿庠通过贩毒积累了大量财富,三年多时间里非法所得超过1亿美元。林睿庠于2024年5月18日在美国被捕,并面临四项罪名指控,一旦定罪将面临至少终身监…

数据库系统概论(十四)详细讲解SQL中空值的处理

数据库系统概论&#xff08;十四&#xff09;详细讲解SQL中空值的处理 前言一、什么是空值&#xff1f;二、空值是怎么产生的&#xff1f;1. 插入数据时主动留空2. 更新数据时设置为空3. 外连接查询时自然出现 三、如何判断空值&#xff1f;例子&#xff1a;查“漏填数据的学生…