线程池ThreadPoolExector核心ctl, execute, addWorker, reject源码分析
线程池核心方法execute()解析:
public void execute(Runnable command) {//#1
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {//#2
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//#3
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))//#4
reject(command);
}
先简要分析一下:
- #1:execute方法接收一个Runnable接口的对象作为任务。
- #2:首先判断当前线程数量是否超出核心线程数,若没有超出,则调用addWorker()方法,去创建一个worker(线程)去执行这个Runnable任务。
- #3:否则将任务加入等待队列,若加入等待队列成功:
- 若当前线程池没有处于运行状态,那么将任务移出队列,并且执行拒绝策略
- 若当前线程池没有线程,那么调用addworker()创建
- #4 否则(若添加队列失败),添加救急线程(核心线程之外的线程),若救急线程添加不成功,则执行拒绝
通过分析,线程池工作流程是符合我们认知的。
下图转载自https://www.cnblogs.com/trust-freedom/p/6681948.html#label_3_1

分析完线程池核心方法,带着以下疑问去展开深入:
- ctl变量是什么?
- addWorker方法做了什么?
- reject方法做了什么?
1. ctl变量
看一下ctl变量和ctlof方法:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
ctl是一个原子整型数。官方注释很长全英文且没分段且标点不清晰,所以这里总结一下:
一个整型数包含了两个线程池重要状态:
- workCount: 有效线程数
- runState: 线程池工作状态,是否在工作或者shut down等等
如何做到的?
首先,一个整型数,占4个byte,也就是二进制32位,官方文档将一个整型数拆成两块来用,我们看一下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
COUNT_BITS=Integer.SIZE - 3,也就是29,将状态位左移29位也就是说:
- 定义高3位为状态位,用来表示线程池5个工作状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
- 定义低29位为workCount,也就是说workCount最大为(2^29)-1,约五百万个(说如果以后不够用的话,把int换成long就行了)。
COUNT_MAST是把1左移29位后减去1,也就是高3位为0,低29位为1,实际上就相当于掩码
那么只需要通过掩码取出高3位或者低29位,即可获取runState和workerCount了:
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~COUNT_MASK; }
private static int workerCountOf(int c) { return c & COUNT_MASK; }
然后通过原子类的方法,进行一系列的状态判断与改变:
private static boolean runStateLessThan(int c, int s) {return c < s;}
private static boolean runStateAtLeast(int c, int s) {return c >= s;}
private static boolean isRunning(int c) {return c < SHUTDOWN;}
private boolean compareAndIncrementWorkerCount(int expect) {return ctl.compareAndSet(expect, expect + 1);}
private boolean compareAndDecrementWorkerCount(int expect) {return ctl.compareAndSet(expect, expect - 1);}
private void decrementWorkerCount() {ctl.addAndGet(-1);}
啥也不说了,高!
2. addWorker方法
addWorker方法判断条件过于复杂(可读性很差),全面的分析我将其折叠:
// 添加工作线程,如果返回false说明没有新创建工作线程,如果返回true说明创建和启动工作线程成功
private boolean addWorker(Runnable firstTask, boolean core) {
点击打开折叠
retry:
// 注意这是一个死循环 - 最外层循环
for (int c = ctl.get();;) {
// 这个是十分复杂的条件,这里先拆分多个与(&&)条件:
// 1. 线程池状态至少为SHUTDOWN状态,也就是rs >= SHUTDOWN(0)
// 2. 线程池状态至少为STOP状态,也就是rs >= STOP(1),或者传入的任务实例firstTask不为null,或者任务队列为空
// 其实这个判断的边界是线程池状态为shutdown状态下,不会再接受新的任务,在此前提下如果状态已经到了STOP、或者传入任务不为空、或者任务队列为空(已经没有积压任务)都不需要添加新的线程
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
// 注意这也是一个死循环 - 二层循环
for (;;) {
// 这里每一轮循环都会重新获取工作线程数wc
// 1. 如果传入的core为true,表示将要创建核心线程,通过wc和corePoolSize判断,如果wc >= corePoolSize,则返回false表示创建核心线程失败
// 1. 如果传入的core为false,表示将要创非建核心线程,通过wc和maximumPoolSize判断,如果wc >= maximumPoolSize,则返回false表示创建非核心线程失败
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 成功通过CAS更新工作线程数wc,则break到最外层的循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 走到这里说明了通过CAS更新工作线程数wc失败,这个时候需要重新判断线程池的状态是否由RUNNING已经变为SHUTDOWN
c = ctl.get(); // Re-read ctl
// 如果线程池状态已经由RUNNING已经变为SHUTDOWN,则重新跳出到外层循环继续执行
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// 如果线程池状态依然是RUNNING,CAS更新工作线程数wc失败说明有可能是并发更新导致的失败,则在内层循环重试即可
// else CAS failed due to workerCount change; retry inner loop
}
}
// 标记工作线程是否启动成功
boolean workerStarted = false;
// 标记工作线程是否创建成功
boolean workerAdded = false;
Worker w = null;
try {
// 传入任务实例firstTask创建Worker实例,Worker构造里面会通过线程工厂创建新的Thread对象,所以下面可以直接操作Thread t = w.thread
// 这一步Worker实例已经创建,但是没有加入工作线程集合或者启动它持有的线程Thread实例
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 这里需要全局加锁,因为会改变一些指标值和非线程安全的集合
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
// 这里主要在加锁的前提下判断ThreadFactory创建的线程是否存活或者判断获取锁成功之后线程池状态是否已经更变为SHUTDOWN
// 1. 如果线程池状态依然为RUNNING,则只需要判断线程实例是否存活,需要添加到工作线程集合和启动新的Worker
// 2. 如果线程池状态小于STOP,也就是RUNNING或者SHUTDOWN状态下,同时传入的任务实例firstTask为null,则需要添加到工作线程集合和启动新的Worker
// 对于2,换言之,如果线程池处于SHUTDOWN状态下,同时传入的任务实例firstTask不为null,则不会添加到工作线程集合和启动新的Worker
// 这一步其实有可能创建了新的Worker实例但是并不启动(临时对象,没有任何强引用),这种Worker有可能成功下一轮GC被收集的垃圾对象
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 把创建的工作线程实例添加到工作线程集合
workers.add(w);
int s = workers.size();
// 尝试更新历史峰值工作线程数,也就是线程池峰值容量
if (s > largestPoolSize)
largestPoolSize = s;
// 这里更新工作线程是否启动成功标识为true,后面才会调用Thread#start()方法启动真实的线程实例
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果成功添加工作线程,则调用Worker内部的线程实例t的Thread#start()方法启动真实的线程实例
if (workerAdded) {
t.start();
// 标记线程启动成功
workerStarted = true;
}
}
} finally {
// 线程启动失败,需要从工作线程集合移除对应的Worker
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
转载自https://www.throwx.cn/2020/08/23/java-concurrency-thread-pool-executor/
我们分析其核心部分:
将Runnable对象的任务提交给一个Worker,并提取出该Worker的成员线程为t:
w = new Worker(firstTask);
final Thread t = w.thread;
如果成功添加工作线程,则调用Worker内部的线程实例t的Thread.start()方法启动真实的线程实例:
if (workerAdded) {
t.start();
workerStarted = true;
}
然后我们在去看看Worker类是什么情况(我理解实际上就是Thread包装了一层类似代理的壳子。。):
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
所以addWorker的核心部分就是通过Thread(Runnable)的方式,创建一个Thread,然后调用这个Thread的start方法,而我们都知道start方法会去调用我们实现Runnable接口时重写的run方法,这就是将任务提交给线程池的本质。
3. reject方法
final void reject(Runnable command) { handler.rejectedExecution(command, this); }
套了层壳。。。调用了RejectedExecutionHandler的一个实例方法
那我们继续深挖handler:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
...好吧,继续深挖实现类。
一共4个实现类,对应4种拒绝策略,终于在里面找到rejectedExecution方法了,挨个分析:
CallerRunsPolicy: 谁(线程)提交的这个任务,谁去执行:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
确实是这样,如果不通过start方法,而直接调用run方法,那么不会创建线程,也就是在主线程中去执行我们重写的run方法。
AbortPolicy: 直接丢弃该任务(什么都不做),抛出一个异常:
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
除了抛出异常外,什么都没有做
DiscardPolicy: 直接丢弃该任务,什么都不做,异常也不抛出
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
看看,拒绝执行函数干脆就是个空的。。。
DiscardOldestPolicy: 喜新厌旧策略
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
等待队列的队头出队,然后执行新来的这个任务。
请指正补充。。
线程池ThreadPoolExector核心ctl, execute, addWorker, reject源码分析的更多相关文章
- java线程池源码分析
我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...
- netty源码分析 - Recycler 对象池的设计
目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...
- java线程池ThreadPoolExector源码分析
java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...
- 通过Thread Pool Executor类解析线程池执行任务的核心流程
摘要:ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. 本文分享自华为云社区<[高并发] ...
- Java 线程池框架核心代码分析--转
原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...
- Java 线程池框架核心代码分析
前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的.线程池应运而生,成为我们管理线程的利器.Java 通过Executor接口,提供了一种标准的方法将任务的提交过 ...
- Java线程池ThreadPoolExector的源码分析
前言:线程是我们在学习java过程中非常重要的也是绕不开的一个知识点,它的重要程度可以说是java的核心之一,线程具有不可轻视的作用,对于我们提高程序的运行效率.压榨CPU处理能力.多条线路同时运行等 ...
- Java面试必问之线程池的创建使用、线程池的核心参数、线程池的底层工作原理
一.前言 大家在面试过程中,必不可少的问题是线程池,小编也是在面试中被问啥傻了,JUC就了解的不多.加上做系统时,很少遇到,自己也是一知半解,最近看了尚硅谷阳哥的课,恍然大悟,特写此文章记录一下!如果 ...
- Java线程池中submit() 和 execute()方法的区别
两个方法都可以向线程池提交任务, execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorS ...
随机推荐
- 攻防世界-MISC:坚持60s
这是攻防世界新手练习区的第六题,题目如下: 点击附件1下载,是一个java文件,点击运行一下: 绿帽子满天飞不知道是怎么回事(还是老老实实去看WP吧),WP说这是编译过的Java代码,但我手里没有反编 ...
- Bugku CTF练习题---MISC---宽带信息泄露
Bugku CTF练习题---MISC---宽带信息泄露 flag:053700357621 解题步骤: 1.观察题目,下载附件 2.下载到电脑里发现是一个bin文件,二进制文件的一个种类,再看名称为 ...
- 漫谈 HTTP 连接
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 本文首先会 HTTP 的特点和优缺点,然后会详细介绍 HTTP 长连接和短连接的连接管理,通过 ...
- 动手实操丨RC522射频卡模块与IC卡完成充值消费查询的技术实现思路
摘要:一文手把手教你利用RC522射频卡模块与IC卡完成充值消费查询的技术实现思路. 本文分享自华为云社区<RC522射频卡模块与IC卡完成充值消费查询的技术实现思路 ...
- JuiceFS 在数据湖存储架构上的探索
大家好,我是来自 Juicedata 的高昌健,今天想跟大家分享的主题是<JuiceFS 在数据湖存储架构上的探索>,以下是今天分享的提纲: 首先我会简单的介绍一下大数据存储架构变迁以及它 ...
- OpenHarmony 3GPP协议开发深度剖析——一文读懂RIL
(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点)本文转载自:https://harmonyos.51cto.com/posts/10608 夏德旺 软通动力信息技术(集 ...
- 详细剖析pyecharts大屏的Page函数配置文件:chart_config.json
目录 一.问题背景 二.揭开json文件神秘面纱 三.巧用json文件 四.关于Table图表 五.同步讲解视频 5.1 讲解json的视频 5.2 讲解全流程大屏的视频 5.3 讲解全流程大屏的文章 ...
- 590. N-ary Tree Postorder Traversal - LeetCode
Question 590. N-ary Tree Postorder Traversal Solution 题目大意:后序遍历一个树 思路: 1)递归 2)迭代 Java实现(递归): public ...
- 04 Springboot 格式化LocalDateTime
Springboot 格式化LocalDateTime 我们知道在springboot中有默认的json解析器,Spring Boot 中默认使用的 Json 解析技术框架是 jackson.我们点开 ...
- while和for循环的补充与数据类型的内置方法(int, float, str)
目录 while与for循环的补充 while + else 死循环 while的嵌套 for补充 range函数 break与continue与else for循环的嵌套 数据类型的内置方法 int ...