ThreadPoolExecutor类
ThreadPoolExecutor简介
可以说ThreadPoolExecutor 就是线程池,实现类ThreadPoolExecutor实现最复杂的运行部分,如下关系图
ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务,所以我们关注点
1.线程池的生命周期管理
2.线程池管理线程和任务
线程池生命周期管理
ctl变量
线程池运行的状态,并不是显式设置的,像Thread类枚举线程状态值,而是伴随着线程池的运行,由内部计算得来这样维护的。
线程池内部使用一个变量AtomicInteger类型ctl 维护两个值:运行状态(runState) 和 线程数量(workerCount),高3位(左边3位)保存runState,低29位(右边29位)保存workerCount,用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源
public class ThreadPoolExecutor extends AbstractExecutorService {
//ctl是一个AtomicInteger类型 计算方法ctlOf(RUNNING, 0) 参数为运行状态 和 线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) {
return rs | wc; }
}
ctlOf(int rs, int wc) 方法解读
- rs (run state): 表示线程池的状态,占用整数的高3位
- wc (worker count): 表示线程池中工作线程的数量,占用整数的低29位
- 位运算符 | 是按位 或 运算符,它将两个整数的每一位进行或运算。如果两个相应的二进制位中至少有一个为1,则结果为1;否则为0
假设 rs 和 wc 的值分别为:
- rs = 010 (二进制,表示 TIDYING 状态)
- wc = 0000000000000000000000000000101 (二进制,表示 5 个工作线程数)
最终结果 0100000000000000000000000000101 就是合并后的 ctl 值,其中高3位表示线程池状态,低29位表示工作线程数
线程池工作线程数量
//Integer.SIZE:32 表示线程池是一个32位的管理单位,COUNT_BITS:29 低29位 (右边29位)
private static final int COUNT_BITS = Integer.SIZE - 3;
//定义静态私有化常量CAPACITY
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
CAPACITY:低29位全为1的掩码,值为 (1 << COUNT_BITS) - 1(即 2^29 - 1 = 536,870,911)
CAPACITY = 000 1111111111111111111111111111111 = 536,870,911
类中定义线程数量计算方法
//计算当前线程数量 参数c是ctl变量
private static int workerCountOf(int c) {
return c & CAPACITY; }
&是一个按位 与 运算符,用于对两个整数的每一位进行逻辑与操作。只有当两个相应的二进制位都为1时,结果才为1,否则为0
c是传入的ctl变量,workerCountOf方法是:我们通过 与& 运算符进行获取 实际的线程工作数量
假设int c =101 0000000000000000000000000000101
计算逻辑是
ctl :101 0000000000000000000000000000101
CAPACITY :000 1111111111111111111111111111111
按位与&计算结果:000 0000000000000000000000000000101
由于CAPACITY前三位都是0 所以与ctl按照&计算 高3位都是0
由于CAPACITY后29位都是1 所以与ctl按照&计算 后29位只需要看ctl的后29位
线程池状态
32位ctl变量,高三位代表RunSate(运行状态)
// 运行状态runstat 每个状态对应的高3位的不同值
private static final int RUNNING = -1 << COUNT_BITS; // 111 000...000
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000 000...000
private static final int STOP = 1 << COUNT_BITS; // 001 000...000
private static final int TIDYING = 2 << COUNT_BITS; // 010 000...000
private static final int TERMINATED = 3 << COUNT_BITS; // 011 000...000
类中定义了运行状态分5种
涉及到的方法如下
// 计算当前运行状态
private static int runStateOf(int c) {
return c & ~CAPACITY; }
反运算符(~)会对操作数的每一位进行取反操作 如011–>110
那么 ~CAPACITY 就变成 111 0000000000000000000000000000000
假设int c =101 0000000000000000000000000000101
ctl :101 0000000000000000000000000000101
~CAPACITY :111 0000000000000000000000000000000
按位与&计算结果:101 0000000000000000000000000000000
由于~CAPACITY前三位都是1 所以与ctl按照&计算 高3位只需要看ctl的前3位
由于~CAPACITY后29位都是0 所以与ctl按照&计算 后29位都是0
线程池工作状态转换方法
线程池状态转换以及涉及到的方法
状态转化图
shutdown() 调用关闭线程池 RUNNING → SHUTDOWN
关键方法:advanceRunState(SHUTDOWN)通过 CAS 更新 ctl 的高3位状态为 SHUTDOWN
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
advanceRunState(SHUTDOWN); // 状态转换:RUNNING → SHUTDOWN
interruptIdleWorkers(); // 中断空闲线程
onShutdown(); // 钩子方法(空实现,可扩展)ThreadPoolExecutor类中定义是由ScheduledThreadPoolExecutor拓展实现的
} finally {
mainLock.unlock();
}
}
shutdownNow() 强制关闭线程池 RUNNING/SHUTDOWN → STOP
关键方法:advanceRunState(STOP)强制更新状态为 STOP,并中断所有工作线程
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
advanceRunState(STOP); // 状态转换:RUNNING/SHUTDOWN → STOP
interruptWorkers(); // 中断所有线程
tasks = drainQueue(); // 清空任务队列
} finally {
mainLock.unlock();
}
tryTerminate(); // 尝试进入终止流程
return tasks;
}
tryTerminate() 任务和线程清理完成后尝试终止线程池
触发条件:当线程池状态为 SHUTDOWN 或 STOP,且任务队列为空、工作线程数为 0
状态转化:SHUTDOWN/STOP → TIDYING → TERMINATED
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 条件检查:状态为 SHUTDOWN/STOP,且任务队列为空、工作线程数为0
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) {
return;
}
// 若仍有活动线程,尝试中断一个线程后退出
if (workerCountOf(c) != 0) {
interruptIdleWorkers(ONLY_ONE);
return;
}
// 更新状态为 TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated(); // 钩子方法,子类可扩展
} finally {
ctl.set(ctlOf(TERMINATED, 0)); // 最终状态:TERMINATED
termination.signalAll(); // 唤醒等待线程
}
return;
}
}
}
terminated() 钩子方法,由子类覆盖 TIDYING → TERMINATED
protected void terminated() {
// 空方法,子类可覆盖(如记录日志、释放资源)
}
作用:在状态变为 TIDYING 后调用,执行自定义清理逻辑,之后自动进入 TERMINATED
任务管理
ThreadPoolExecutor运行机制图
任务分配
整个Execute框架在顶级接口Executor中定义了execute方法,在ThreadPoolExecutor实现类中重写实现了该方法,是线程池的主要入口,用户提交了一个任务,之后这个任务将如何都是由这个阶段决定,结合addWorker和runWorker完成线程创建与任务执行
public void execute(Runnable command) {
//任务单元 线程是否存在
if (command == null)
throw new NullPointerException();
int c = ctl.get(); // 获取当前ctl值(状态 + 线程数)
// 1.判断核心线程数是否已达到,如果没达到 尝试添加到核心线程队列中去
if (workerCountOf(c) < corePoolSize) {
// 添加核心线程(第二个参数为true) 该方法返回的是布尔值
if (addWorker(command, true))
return;
c = ctl.get(); // 若添加失败(如并发场景),重新获取ctl
}
// 2. 当核心线程已满 尝试将任务放入工作队列
if (isRunning(c) && workQueue.offer(command)) {
// 线程池为RUNNING且入队成功
// 二次检查状态(防止并发关闭)
int recheck = ctl.get();
// 若线程池已关闭,回滚队列并拒绝任务
if (!isRunning(recheck) && remove(command))
reject(command);
// 若线程数为0(如corePoolSize=0),创建非核心线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. 如果队列已满,并且当前线程数小于最大线程数(maximumPoolSize)尝试创建非核心线程
else if (!addWorker(command, false)) // 添加非核心线程(第二个参数为false)
// 4. 创建非核心线程也失败,触发拒绝策略(队列和线程均满)
reject(command);
}
线程池分配任务机制:
- 核心线程数检查:当任务提交时,首先检查当前工作线程数是否小于核心线程数(corePoolSize)。如果是,则尝试创建新线程执行任务。
- 任务入队:如果核心线程已满,尝试将任务放入工作队列。这里需要考虑线程池的状态是否允许入队,比如在SHUTDOWN状态下是否接受新任务。
- 非核心线程创建:如果队列已满,并且当前线程数小于最大线程数(maximumPoolSize),则创建非核心线程来执行任务。
- 拒绝策略:如果队列和线程数都已满,则执行拒绝策略,比如抛出异常或丢弃任务
在这里 还需要解释下两个内容
1.线程池7个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,//非核心线程存活时间单位
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//核心线程默认情况下会一直存在于线程池中,即使这些核心线程不执行任何任务
private volatile int corePoolSize;
//线程最大数,当核心线程和队列都满了,会根据线程数是否达到最大线程数,如果没有才会创建非核心线程
private volatile int maximumPoolSize;
// 非核心线程数存活时间 在工具类Executors.newCachedThreadPool()方法中默认60秒
private volatile long keepAliveTime;
//阻塞队列 核心线程满了会将任务放入阻塞队列中,构造方法会给size值
private final BlockingQueue<Runnable> workQueue;
//创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
private volatile ThreadFactory threadFactory;
//拒绝策略(handler) 当核心线程 队列 非核心都达到了 在无法接受新任务时候的拒绝新任务方式
private volatile RejectedExecutionHandler handler;
注意:ThreadPoolExecutor 允许运行时修改参数:
executor.setCorePoolSize(6); // 调整核心线程数
executor.setMaximumPoolSize(12); // 调整最大线程数
executor.setKeepAliveTime(30, TimeUnit.SECONDS); // 调整非核心线程存活时间
核心线程和非核心线程在Java中的区别在于它们的生命周期和在线程池中的行为
①核心线程默认情况下会一直存在于线程池中,即使这些核心线程不执行任何任务(铁饭碗)
②非核心线程如果长时间处于闲置状态就会被销毁(临时工)。配置的时间内,非核心线程也会销毁,哪怕任务没有执行完,那没有执行完的任务会进入任务队列
③如果设置了allowCoreThreadTimeOut为true,那么这个配置也会作用于核心线程,核心线程可能被回收,导致实际存活线程数降到 0,但 corePoolSize 的配置值不会改变
2.addWorker方法解读
在满足线程池状态和容量限制的条件下,安全地增加工作线程
addWorker 方法返回值是布尔类型 成功创建并启动线程返回 true,否则返回 false
private boolean addWorker(Runnable firstTask, boolean core)
- 参数:
①firstTask:这是一个Runnable类型的参数,代表了需要执行的第一个任务(Runnable不是线程, Runnable 只是一个任务接口(run方法),Thread才是线程),可以为null,firstTask为null意味着新创建的线程将从任务队列中获取任务来执行
②core:是否以核心线程数(corePoolSize)为上限。true 表示使用核心线程限制,false 表示使用最大线程数(maximumPoolSize)。
- 返回值:成功创建并启动线程返回 true,否则返回 false
其实现逻辑分三步
1.状态检查: 检查线程池状态是否允许创建新线程
retry: //搭配for (;;) while等使用 代表循环从哪里开始结束
for (;;) {
//for (;;)永真循环或无限循环 遇到break return这类结束循环
int c = ctl.get();
int rs = runStateOf(c); //获取状态
// 检查线程池状态是否允许创建新线程
if (rs >= SHUTDOWN //状态大于或等于shutdown(开始关闭状态),也就是非Running运行状态
&&
//线程池状态是 SHUTDOWN 但队列中还有任务或者 firstTask 为空
! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))
return false;
}
拒绝条件两个按 &&并且 执行
①左侧的条件好理解 rs >= SHUTDOWN:线程池处于 SHUTDOWN、STOP、TIDYING 或 TERMINATED 状态
②右侧的条件! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
SHUTDOWN状态下,只有当队列非空且需要创建无初始任务的线程时,才允许创建新线程,这里仅仅是允许,不是就创建了新线程
为什么SHUTDOWN状态下,还有能允许创建新线程的情况?
当线程池调用 shutdown() 进入 SHUTDOWN 状态时行为规则:
- 不再接受新任务:后续提交的任务会被拒绝(触发拒绝策略)。
- 继续处理队列中的剩余任务:已入队的任务会被现有工作线程处理完毕。
- 不创建新线程:即使队列中有任务且当前线程数不足,线程池也不会创建新线程。
但是存在一种情况现有线程退出导致工作线程数为 0 时,但是队列中还有任务没有处理完的
- 工作线程数为 0(例如核心线程数为 0 且 allowCoreThreadTimeOut=true)
- 队列非空且线程池状态为 SHUTDOWN
在 execute() 方法中,若任务入队后检测到工作线程数为 0,会调用 addWorker(null, false) 创建新线程处理队列任务
// 若线程数为0(如corePoolSize=0),创建非核心线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
在 SHUTDOWN 状态下,优先利用现有线程处理队列任务,哪怕队列里面任务比较多线程比较少
也不创建新线程,因为线程池即将终止了
兜底机制:若所有线程意外退出,强制创建一个非核心线程处理剩余任务,防止队列任务“饿死”。
2. 线程数限制检查
for (;;) {
int wc = workerCountOf(c);
// 检查是否超过容量或线程数上限 //core参数选择 corePoolSize 或 maximumPoolSize 作为上限
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS增加工作线程数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get();
// 状态变化则重试外层循环
if (runStateOf(c) != rs)
continue retry;
}
3. 创建并启动Worker
Worker w = new Worker(firstTask);
Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
workers.add(w); // 添加到工作线程集合
} finally {
mainLock.unlock();
}
t.start(); // 启动线程
return true;
}
Worker对象:封装任务和执行线程,继承 AQS 实现不可重入锁。
加锁操作:使用 mainLock 确保对 workers 集合的线程安全访问。
4. 失败回滚
finally {
if (! workerStarted)
addWorkerFailed(w);
}
回滚逻辑:
①若 Worker 已创建,从 workers 集合中移除
②CAS减少工作线程数(CAS 乐观锁 实现了并发安全特性的原子性)
③尝试终止线程池(tryTerminate())
任务缓冲
任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务
阻塞队列(BlockingQueue):生产者是往队列里添加任务的线程,消费者是从队列里拿任务的线程。阻塞队列就是生产者存任务的容器,而消费者也只从容器里拿任务
是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景
阻塞队列的类型
1.ArrayBlockingQueue:这是一个基于数组的有界阻塞队列,此队列遵循FIFO先进先出原则
2.LinkedBlockingQueue:这是一个基于链表的阻塞有界队列,此队列遵循FIFO先进先出原则,此队列默认长度Integer.MAX_VALUE,所以默认创建此队列有容量风险
3.PriorityBlockingQueue:这是一个基于优先级排序的无界阻塞队列。元素按照优先级进行排序,优先级高的元素会优先被取出,也可以自定义compareTo()方法进行优先级排序,不保证同一优先级的元素顺序
4.SynchronousQueue:这不是一个真正的队列,而是一个不存储元素的传递队列。每个 put 操作必须等待一个 take 操作,否则不能添加新元素,反之亦然。它主要用于在线程间进行直接传递数据的场景
5.DelayQueue:这是一个使用Delayed元素的无界阻塞队列。队列中的元素只有在其延迟到期后才能被消费
6.LinkedTransferQueue:这是一个由链表结构组成的无界阻塞队列,相比其他队列,多了tranfer和tryTranfer方法
7.LinkedBlockingDeque:这是一个由链表结构组成的双向阻塞队列,队头和队尾都可以添加 移除元素,多线程并发时 可以将锁的竞争降低到一半
任务获取
线程池对于任务管理包括
1.核心线程数和非核心线程在创建线程之后,就会启动线程工作,执行(Runnable firstTask)任务
实现逻辑就是上面讲的在addWorker方法中
private boolean addWorker(Runnable firstTask, boolean core) {
if (workerAdded) {
t.start();
workerStarted = true;
}
}
2.对于阻塞队列中的任务是如何进行获取任务和执行的,这里是如何进行管理的
getTask()方法是工作线程(Worker)从任务队列获取任务的核心逻辑,负责管理线程的生命周期和任务的获取策略
1.
private Runnable getTask() {
boolean timedOut = false; // 标记上次poll是否超时
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 1. 状态检查:线程池已关闭且队列为空,或状态为STOP及以上
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount(); // 减少工作线程数
return null; // 触发线程退出
}
// 当前工作线程数
int wc = workerCountOf(c);
// 2. 判断是否允许超时(非核心线程或允许核心线程超时)
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 3. 检查是否需要回收线程:
// a. 线程数超过最大值(因配置动态调整)
// b. 允许超时且上次poll超时
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// CAS减少线程数,返回null触发线程退出
if (compareAndDecrementWorkerCount(c))
return null;
continue; // CAS失败则重试
}
try {
// 4. 从队列获取任务:超时等待或阻塞等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r; // 成功获取任务
timedOut = true; // 标记超时
} catch (InterruptedException retry) {
timedOut = false; // 中断后重置超时标记
}
}
}
步骤总结
1. 线程池状态检查
条件:若线程池状态为 SHUTDOWN 且队列为空,或状态为 STOP/TIDYING/TERMINATED
行为:减少工作线程数,返回 null,触发当前线程退出
目的:确保关闭状态下不再处理新任务,队列为空时终止线程
2. 超时允许判断
timed 计算:
- allowCoreThreadTimeOut=true:核心线程允许超时回收。
- wc > corePoolSize:当前线程数超过核心线程数,视为非核心线程。
结果:timed的值取决于 后续使用 poll()(超时获取)还是 take()(阻塞获取)
3. 线程回收条件
条件:
- 线程数超过最大值:可能因动态调整 maximumPoolSize 导致。
- 允许超时且上次获取任务超时(timed && timedOut)。
行为:通过 CAS 减少工作线程数,返回 null 终止当前线程。
目的:动态回收空闲或多余的线程,优化资源使用
4. 从队列获取任务
策略:
timed=true:调用 poll(keepAliveTime, TimeUnit),超时返回 null。
timed=false:调用 take(),阻塞直到获取任务或中断。
异常处理:若捕获 InterruptedException,重置超时标记并重试
任务拒绝
任务拒绝是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池
拒绝策略是一个接口
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
其实现JDK有4个类,所以我们可以自定义实现拒绝策略,或使用自带的4种拒绝策略
自带的4种拒绝策略说明
1.AbortPolicy类
也是默认使用的拒绝策略
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() {
}
//直接抛出RejectedExecutionException异常,并终止新任务的提交
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
AbortPolicy 通过在 rejectedExecution 方法中抛出 RejectedExecutionException 异常来终止新任务的提交。关键业务推荐使用,严格监控任务提交失败
2.DiscardPolicy类
静默丢弃,允许任务丢失的非关键场景可以使用
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() {
}
//终止新任务的提交 但是不抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
3.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);
}
}
}
优先处理新任务,容忍旧任务丢失,但可能丢失重要任务,慎重使用
4.CallerRunsPolicy类
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() {
}
//调用者线程(提交任务的线程)处理该任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
优点是:控制提交速度,避免过载
缺点是:可能阻塞调用者线程