ThreadPoolExecutor源码分析
ThreadPoolExecutor源码分析
上一篇(https://www.cnblogs.com/reecelin/p/12334107.html)中,我们对ThreadPoolExecutor的使用有了初步了解,今天我们来详细解剖一下ThreadPoolExecutor的源码,分析其机制和原理。
属性
// 下面详细解释
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;//32-3=29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;//2^29 - 1 约5亿
// 线程池状态 runState使用高位存储
//
//RUNNING;该状态的线程池接收新任务,并且处理阻塞队列中的任务
private static final int RUNNING = -1 << COUNT_BITS;//高3位为:111
//SHUTDOWN;该状态的线程池不接收新任务,但会处理阻塞队列中的任务;
private static final int SHUTDOWN = 0 << COUNT_BITS;//高3位为:000
//STOP;不接收新任务,也不处理阻塞队列中的任务,并且会中断正在运行的任务;
private static final int STOP = 1 << COUNT_BITS;//高3位为:001
//所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态
private static final int TIDYING = 2 << COUNT_BITS;//高3位为:010
//线程池彻底终止,就变成TERMINATED状态
private static final int TERMINATED = 3 << COUNT_BITS;//高3位为:011
.
.
.
重点说说ctl这个属性。它是一个AtomicInteger类型的变量,在这里代表了两个属性:
- workCount:线程池中有效的线程数;
- runState:线程池状态;
问题来了,为什么ctl可以同时表示数量和状态呢?主要是为了在保证性能时还尽可能的高效利用内存空间。因此常常会用一个变量表示多种业务状态。在源码中有这么一段话,翻译过来如下:
为了让有效线程数和线程池的状态能够用一个int变量表示,将线程数限制在了2^29-1(约为5亿),这样的话就可以用低29位来表示有效线程数,高3位来表示线程的状态。
基于源码,可以得出它们的实际值:
COUNT_BITS = Integer.SIZE - 3;即:COUNT_BITS = 32 -3 = 29;
CAPACITY = (1 << 29) - 1;即:CAPACITY = 2^29 - 1 约等于5亿;
RUNNING = -1 << 29; --> 高3位为:111
SHUTDOWN = 0 << 29; --> 高3位为:000
STOP = 1 << 29; --> 高3位为:001
TIDYING = 2 << 29; --> 高3位为:010
TERMINATED= 3 << 29; --> 高3位为:011
再看一下几个计算方法
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
runStateOf方法用来获取线程池的状态,~CAPACITY获得的值就是高3位为1,低29位为0。因此 c & ~CAPACITY计算得到的结果就只有c的高3位,这样runState的值就从ctl中解析出来了,可用于表示线程池的状态;同理,workerCountOf()方法获取到的实际上就是ctl低29位的值。
线程池的生命周期如下图所示:
工作原理
execute
日常开发过程中,我们使用execute方法来提交任务。我们先来看一下execute方法的执行过程。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取ctl值
int c = ctl.get();
// 如果线程池中线程数小于 corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 创建一个线程来处理该任务
if (addWorker(command, true))
return;
// 再次获取ctl值
c = ctl.get();
}
// 如果线程池处于运行状态,且任务成功被加入到队列当中
if (isRunning(c) && workQueue.offer(command)) {
//双重校验 ctl
int recheck = ctl.get();
//如果现场池已经shutdown,且将任务移除
if (! isRunning(recheck) && remove(command))
//拒绝这个任务
reject(command);
// 如果线程数量为0
else if (workerCountOf(recheck) == 0)
// 则新建线程
addWorker(null, false);
}
// 如果加入队列失败
else if (!addWorker(command, false))
//拒绝任务
reject(command);
}
execute
主要分3个步骤:
- 当线程池中线程数小于corePoolSize时,将调用addWorker方法创建新的线程;
- 如果线程池处于运行状态,且成功加入到队列当中,重新校验一下runState;
- 如果此时线程池处于shutDown状态,且成功将任务从队列中移除,则使用拒绝策略拒绝该任务;
- 如果此时线程池线程数量为0,则新建线程
- 最后,如果加入队列失败,则使用拒绝策略拒绝该任务;
addWoker
当线程池中线程池数小于核心线程池数,或者是新建非核心线程池,都会调用addWoker方法用于新建线程。这个方法有两个参数:
- firstTask 当前任务
- core 用来标注当前创建的线程是否是核心线程,如果core为true,则表明创建的是核心线程,也就是说当前还没有达到最大核心线程数
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//自旋
for (;;) {
int c = ctl.get();
// 线程池状态
int rs = runStateOf(c);
//如果状态是STOP,TIDYING,TERMINATED状态的话,则会返回false 不会再新建线程
//如果状态是SHUTDOWN,但是firstTask不为空或者workQueue为空的话,那么直接返回false 不新建线
//程
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//通过自旋的方式,判断要添加的worker是否为corePool范畴之内的
for (;;) {
// 线程数量
int wc = workerCountOf(c);
// 如果线程数量已超过最大容量
if (wc >= CAPACITY ||
//或者是超过核心线程或最大线程数
wc >= (core ? corePoolSize : maximumPoolSize))
//不再创建线程接受任务
return false;
// CAS操作使workerCount+1,增加成功跳出循环
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//如果不成功,则再次判断当前线程池的状态,如果现在获取到的状态与进入自旋的状态不一致的
//话,那么则通过continue retry重新进行状态的判断
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// workerCount增加成功进入下面的逻辑
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建一个新的Worker对象
w = new Worker(firstTask);
//获取线程
final Thread t = w.thread;
//如果线程不为空
if (t != null) {
//加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 在锁定的情况下重新检查。
int rs = runStateOf(ctl.get());
//如果是运行状态 或者 处于shutdown状态且任务为空
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 预先检查t是可以启动的
if (t.isAlive())
// 抛出异常
throw new IllegalThreadStateException();
//添加至workers中
//private final HashSet<Worker> workers = new HashSet<Worker>()
workers.add(w);
//获取wokers的数量
int s = workers.size();
//如果超过了历史最大线程数,则将当前池数量设置为历史最大线程记录数
// private int largestPoolSize 最大线程记录数
if (s > largestPoolSize)
largestPoolSize = s;
//标识添加工作线程成功
workerAdded = true;
}
} finally {
解锁
mainLock.unlock();
}
//如果添加成功则启动当前工作线程
if (workerAdded) {
t.start();
//并将当前线程状态设置为已启动
workerStarted = true;
}
}
} finally {
//添加失败
if (! workerStarted)
//从wokers中移除并进行状态重置
addWorkerFailed(w);
}
// 返回woker启动状态
return workerStarted;
}
线程池维护的线程其实是一组Worker对象,Worker封装了线程也继承了AbstractQueuedSynchronizer
类并实现了Runnable
接口,重写了void run()
方法。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
//Worker持有的线程
final Thread thread;
//初始化的任务,可以为null
Runnable firstTask;
//已完成任务数
volatile long completedTasks;
Worker(Runnable firstTask) {
//阻止中断,在任务获取前不允许中断
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//worker对象启动时执行的方法
public void run() {
runWorker(this);
}
.
.
.
//中断已开始执行的线程,这个就是为什么要设置setState(-1)的一个原因了,这个方法会被
//shutdownNow()方法调用
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
runWorker方法
runWoker是Worker类中一个方法。Worker是ThreadPoolExecutor的一个静态内部类,它继承了AbstractQueuedSynchronizer,实现了Runnable接口。Worker
类之所以要继承AbstractQueuedSynchronizer
主要是要用锁的状态来区分空闲线程和非空闲线程,在执行runWorker
方法中:
- 获取任务时没有加锁(空闲状态,可中断线程)
- 要执行任务时才加锁(不允许中断线程)
在调用void tryTerminate()
和void shutdown()
这两个方法时,会中断空闲线程,所以没有在执行任务的线程就可能被中断。
final void runWorker(Worker w) {
//获取当前线程
Thread wt = Thread.currentThread();
//获取执行任务
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 将state设置为0,允许中断,与Worker构造函数的setState(-1)是一对的
boolean completedAbruptly = true;// 异常退出标记
try {
// 循环取出任务,如果第一个任务不为空,或者从队列中拿到了任务
// 只要这两个条件满足,会一直循环,直到没有任务,正常退出,或者异常退出
while (task != null || (task = getTask()) != null) {
w.lock();// 该线程标记为非闲置
//1. 如果线程池状态大于等于STOP并且本线程未中断,则应该执行中断方法
//2. 或者执行Thread.interrupted()方法判断本线程是否中断并且清除中断状态,
//如果发现线程池状态大于等于STOP则执行中断方法。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//钩子方法 开发者自行实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//钩子方法 开发者自行实现
afterExecute(task, thrown);
}
} finally {
task = null;
//该线程执行的任务加1,即使抛出异常
w.completedTasks++;
//解锁 表示回到空闲状态
w.unlock();
}
}
//执行到这一步表示是由于获取不到任务而正常退出的,所以completedAbruptly为false
completedAbruptly = false;
} finally {
//无论怎样执行退出
processWorkerExit(w, completedAbruptly);
}
}
getTask方法
private Runnable getTask() {
//表示获取任务是否已超时
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//1. 若线程池状态大于等于停止状态,此时线程池不再处理队列的任务,并且会回收所有线程(不管空不空
//闲),所以此时应该把线程池线程数量减1,并且获取的任务为空
//2. 处于关闭状态且任务队列为空,表示任务队列为空且不会有任务提交,所以线程数减1,并且获取的任务
//为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//是否启用超时机制。当允许核心线程超时或当前线程数超过核心线程则启用
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果线程数量超过线程池所允许的最大线程数或者启用超时机制情况下获取任务超时,理论上应该回收线程。
//但是如果该线程是线程池中的最后一个线程且任务队列不为空就可以不回收,继续运行,要是还有其他线程或
//者任务队列为空则回收该线程。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//尝试将线程数量减1,成功返回null,失败继续从循环开始处开始。这里为什么不是用
//decrementWorkerCount()
//这种不会失败的方法减1而采用这种方式。是因为 wc > 1,如果线程池不只有一个线程它们互相发现
//不只一个线程,且它们同时执行不会失败的将线程数量减一的方法,到时线程池线程数量可能就为0
//了,哪么队列中的任务就没线程执行了。
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//1. 如果启用超时机制就执行poll()方法,在keepAliveTime纳秒内还没获取就返回null。
//2. 如果未启用超时机制就执行take()方法,队列没任务就一直阻塞直到有任务。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
//到这里就是因为超时获取不到任务
timedOut = true;
} catch (InterruptedException retry) {
//在执行take()过程中被中断并不算超时
timedOut = false;
}
}
}
processWorkerExit方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//由于不是获取不到任务而正常退出的,得在这里将线程数减1,正常退出的在getTask()方法有这个减1操作
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
//加锁,因为HashSet和completedTaskCount不是线程安全的
mainLock.lock();
try {
//将线程执行的任务数统一加到线程池维护的completedTaskCount字段
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试将线程池设置为结束状态
tryTerminate();
int c = ctl.get();
//满足当前线程池状态小于STOP(运行或关闭状态)才继续
if (runStateLessThan(c, STOP)) {
若线程是异常退出runWorker方法就直接添加一个没有带初始任务的非核心线程
if (!completedAbruptly) {
//这三行代码找出当前线程池所至少存在的线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//如果当前线程数已经大于等于min,就直接返回,否则添加一个没有带初始任务的非核心线程
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
下图是向线程池提交任务后,线程池的正常执行过程:
shhutdown/shutdownNow
当使用完线程池后,一般会调用shutdown或者shutdownNow方法来关闭线程池。这两个方法有些许区别,我们来分别看一下。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 设置线程池状态为:SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断所有任务线程
interruptIdleWorkers();
onShutdown(); //ScheduledThreadPoolExecutor专用
} finally {
mainLock.unlock();
}
//关闭线程池
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 校验是否具有shutdown权限
checkShutdownAccess();
// 将线程池状态设置为:STOP
advanceRunState(STOP);
// 中断所有线程
interruptWorkers();
// 获取队列中尚未被执行的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//关闭线程池
tryTerminate();
return tasks;
}
两者不同:
- 调用shutdown方法时,线程池将不会接受新的任务。然后将线程池状态设置为SHUTDOWN,最后中断线程,这里的中断是用
Thread.interrupt()
实现的,所以不会影响正在执行的线程,正在执行的的任务将会继续执行; - shutdownNow与shutdown类似,但却是将线程池状态设置为STOP,不管线程是否空闲都将被中断,中断所有线程,但也不会强行终止正在执行的线程。最后返回阻塞队列中没有被执行的任务;
参考:
https://juejin.im/post/58e7544c0ce463005851eb92#heading-8
https://juejin.im/post/5a7abe75f265da4e7e10a19e#heading-5
https://juejin.im/post/5f031c326fb9a07eaf26b696#heading-11
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南