type
status
date
slug
summary
tags
category
icon
password
Property
Feb 26, 2025 01:40 PM
线程池技术原理深度解析,源码作者:Doug Lea

Java线程池底层原理

学习Java,我原来最怕的就是并发,因为我不懂底层原理,但是直面并发底层之后,我发现我对并发底层的设计,开始一点点起了兴趣,发现Doug Lea的代码写的确实不错,教授的水平是真的在线。并发离不开多线程,多线程又离不开线程池。因此,本文将沉浸式带你一起领略,Doug Lea大神的并发线程池底层实现逻辑。
先看这个线程池的局部常量,一起来了解一下。首先是CTL对象,ctl对象存储的是线程池的状态,这样很好理解,你可以从列举出来的五种线程池状态中看出端倪,状态存储使用的是线程安全原子类,AtomicInteger ,底层是Integer存储,Integer默认是4字节长度,一个字节占据8个比特位,一个Integer对象占据32字节比特位。因此,可以使用二进制Integer来表示多种数据,对于状态而言,线程池采用Integer的32为中3位比特来表示五种状态,这里解释一下为什么使用3个比特位:因为要表示五种状态,如果是两个比特位,那么就是2^2 = 4,最多能表达四种状态,如果是三个比特位,那么就是 2^3 = 8,能表达最多八个状态,因此采用三个二进制位来表示五种状态是足够的。
说到这里想必也清楚了为什么会有这段代码了吧,减去3就是这个目的。3代表的就是上面所述的含义。至于各种状态,其实就是位运算,这没有什么神秘的。COUNT_BITS 的核心作用是 划分线程池状态(runState)和有效线程数(workerCount)的二进制位边界。这是查阅了很多资料后给出的结论。线程池状态我们刚讲过了,有效线程数指的是什么呢?用低 29 位用于存储线程池的有效线程数CAPACITY 是有效线程数的最大值(即 2^29 - 1),表示线程池最多可以容纳约 5.3 亿个线程(实际上不会达到这个值)。
源码中存在上面这三行代码,第一行是提取状态;第二行是提取线程数量。第三行将状态(rs)和线程数(wc)合并为一个 int 类型的 ctl 值。
这是线程池中默认的阻塞队列,用于存放当核心线程满了之后的任务队列。
这是底层保证Woker集合操作线程安全的锁,后面会介绍到。
底层使用的HashSet来存储Woker对象的,线程不安全,因此才会有mainLock。
最大线程池内的线程数量,每当创建一个线程,这个值就会刷新。
这就是底层Woker的核心实现,很明显,还是基于AQS实现的。
从总体到局部,接下来看局部。

一、线程池是如何执行任务的?

ThreadPoolExcutor中是通过public void execute(Runnable command) 来执行任务的。execute函数执行流程如下:
notion image
提交一个Runnable时,不管当前线程池中的线程是否空闲,只要数量小于核心线程数就会创建 新线程。ThreadPoolExecutor相当于是非公平的,比如队列满了之后提交的Runnable可能会比正在 排队的Runnable先执行,因为排队中的任务需要等待线程启动,而队列满了之后新的线程在总线程数小于配置的线程数的时候,是会启动新线程来执行的。

二、线程状态是如何切换

线程池一共就只有五种状态,这五种状态并非随意切换,而是需要从上一个状态切换到下一个状态。在线程池初始化的时候,默认是Running状态。线程池的这几种状态下,分别表示什么含义呢?首先是第一个Running状态,该状态下会接收新任务并且会处理队列中的任务;其次是SHUTDOWN状态,这是由于显式调用了shutDown函数之后,线程池的状态切换,从RUNNING状态切换到到了SHUTDOWN状态,此状态不会接受新的任务,但是会完处理队列中的任务。与之对应的则是STOP状态,此状态也是不会接受新的任务,但是他也不会继续处理队列中的任务,相反,他会中断处理中的任务,此状态一般是在RUNNING状态下,显式调用了shutDownNow函数导致的。当线程池中所有的线程都终止了,最后一个线程执行关闭的时候,此时workCount已经是0了,此时会将状态切换到TIDYING状态,一旦到达此状态之后,最后会执行线程池对象的terminated(),也就是线程池会最终关闭,terminated()函数调用了之后,线程池状态切换到最终状态,TERMINATED。

三、线程池中的线程如何关闭

notion image

3.1 常规关闭

常规关闭其实是基于Interrupt去进行中断后自行消亡的。说到这里其实可以参考底层显式调用的函数,shutdown与shutdownNow函数。这两个函数就是:
notion image
notion image
别看这里一个是中断线程,一个是中断空闲线程,首先这印证了我们之前所述,其次,不要迷惑怎么判断空闲与否的,进去看看就知道了:
notion image
看见了没,w.trylock(),在本小节最开始我用蓝色剪头标注了,那里有个unlock操作。很简单的道理:能获取锁,就说明当前线程空闲,锁不起来,说明当前线程不空闲,就是这么简单粗暴。

3.2 异常关闭

如果是因为因为执行过程中抛出异常导致线程关闭,需要看当前线程处于什么状态,如果是RUNNING状态,看当前线程池中已有的线程数量,如果大于核心线程数,并且任务队列已经满了,会自然消亡,不会替补线程。但是如果数量小于核心线程数,会自动替补一个firstTask值为nul的worker。
notion image
其实这里有一个比较有意思的点,我一开始也没有看出来,是通过看别的讲师讲课的时候发现的:
这里,其实存在一个线程补偿机制。上面说了,1异常的时候,此时completedAbruptly 是true,因此会直接走addWorker函数。但是如果completedAbruptly不是true而是false的时候呢?此时c小于STOP,只有两种状态,不是RUNNING就是SHUTDOWN。
notion image
下面的代码意思是,先获取当前状态下,线程池至少需要多少线程,假设走了min=1,那么也就是说可能是如下意思:出于SHUTDOWN状态的线程池中,任务队列还有待完成的线程,因此需要至少一个线程来完成任务,所以还会去统计worker的数量,所以有一个workerCount函数,如果发现当前workerCount数量大于最低要求数量,那么就不需要做线程补偿机制,如果发现小于,则需要add一个worker。
 
未完待续…
相关文章
计算机视觉(一):深度学习的人脸应用
Lazy loaded image
计算机视觉(二):特征向量计算
Lazy loaded image
计算机视觉(三):人脸识别之特征提取
Lazy loaded image
Flowable(一):Java知识学习
Lazy loaded image
Flowable(二):数据库篇
Lazy loaded image
Flowable(三):Liquibase模式管理
Lazy loaded image
JUC核心篇(六):阻塞队列技术杂文(十一):你真的有理解计算机素养吗?
Loading...
fntp
fntp
多一点兴趣,少一点功利
最新发布
JUC核心篇(七):线程池底层原理
2025-2-26
JUC核心篇(六):阻塞队列
2025-2-24
JUC核心篇(四):CAS与AQS
2025-2-22
JUC技术篇(六):Volatile关键字
2025-2-21
JUC技术篇(五):Synchronized锁
2025-2-21
JUC核心篇(三):LockSupport与线程阻塞
2025-2-21
公告
📝 博客只为了记录我的学习生涯
😎 我的学习目标是成为一名极客
🤖 我热爱开源当然我也拥抱开源
💌 我期待能收到你的Email留言
📧 我的邮箱:stickpoint@163.com
欢迎交流~