【高并发】通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程

addWorker(null, false);

(4)如果在步骤(3)中向任务队列中添加任务失败,则尝试开启新的线程执行任务。此时,如果线程池中的线程数量已经大于线程池中的最大线程数maximumPoolSize,则不能再启动新线程。此时,表示线程池中的任务队列已满,并且线程池中的线程已满,需要执行拒绝策略,代码如下所示。

//任务队列已满,则新增worker线程,如果新增线程失败,则执行拒绝策略

else if (!addWorker(command, false))

reject(command);

这里,我们将execute(Runnable)方法拆解,结合流程图来理解线程池中任务的执行流程就比较简单了。可以这么说,execute(Runnable)方法的逻辑基本上就是一般线程池的执行逻辑,理解了execute(Runnable)方法,就基本理解了线程池的执行逻辑。

注意:有关ScheduledThreadPoolExecutor类和ForkJoinPool类执行线程池的逻辑,在【高并发专题】系列文章中的后文中会详细说明,理解了这些类的执行逻辑,就基本全面掌握了线程池的执行流程。

在分析execute(Runnable)方法的源码时,我们发现execute(Runnable)方法中多处调用了addWorker(Runnable, boolean)方法,接下来,我们就一起分析下addWorker(Runnable, boolean)方法的逻辑。

addWorker(Runnable, boolean)方法

总体上,addWorker(Runnable, boolean)方法可以分为三部分,第一部分是使用CAS安全的向线程池中添加工作线程;第二部分是创建新的工作线程;第三部分则是将任务通过安全的并发方式添加到workers中,并启动工作线程执行任务。

接下来,我们看下addWorker(Runnable, boolean)方法的源码,如下所示。

private boolean addWorker(Runnable firstTask, boolean core) {

//标记重试的标识

retry:

for (;😉 {

int c = ctl.get();

int rs = runStateOf©;

// 检查队列是否在某些特定的条件下为空

if (rs >= SHUTDOWN &&

! (rs == SHUTDOWN &&

firstTask == null &&

! workQueue.isEmpty()))

return false;

//下面循环的主要作用为通过CAS方式增加线程的个数

for (;😉 {

//获取线程池中的线程数量

int wc = workerCountOf©;

//如果线程池中的线程数量超出限制,直接返回false

if (wc >= CAPACITY ||

wc >= (core ? corePoolSize : maximumPoolSize))

return false;

//通过CAS方式向线程池新增线程数量

if (compareAndIncrementWorkerCount©)

//通过CAS方式保证只有一个线程执行成功,跳出最外层循环

break retry;

//重新获取ctl的值

c = ctl.get();

//如果CAS操作失败了,则需要在内循环中重新尝试通过CAS新增线程数量

if (runStateOf© != rs)

continue retry;

}

}

//跳出最外层for循环,说明通过CAS新增线程数量成功

//此时创建新的工作线程

boolean workerStarted = false;

boolean workerAdded = false;

Worker w = null;

try {

//将执行的任务封装成worker

w = new Worker(firstTask);

final Thread t = w.thread;

if (t != null) {

//独占锁,保证操作workers时的同步

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

//此处需要重新检查线程池状态

//原因是在获得锁之前可能其他的线程改变了线程池的状态

int rs = runStateOf(ctl.get());

if (rs < SHUTDOWN ||

(rs == SHUTDOWN && firstTask == null)) {

if (t.isAlive())

throw new IllegalThreadStateException();

//向worker中添加新任务

workers.add(w);

int s = workers.size();

if (s > largestPoolSize)

largestPoolSize = s;

//将是否添加了新任务的标识设置为true

workerAdded = true;

}

} finally {

//释放独占锁

mainLock.unlock();

}

//添加新任成功,则启动线程执行任务

if (workerAdded) {

t.start();

//将任务是否已经启动的标识设置为true

workerStarted = true;

}

}

} finally {

//如果任务未启动或启动失败,则调用addWorkerFailed(Worker)方法

if (! workerStarted)

addWorkerFailed(w);

}

//返回是否启动任务的标识

return workerStarted;

}

乍一看,addWorker(Runnable, boolean)方法还蛮长的,这里,我们还是将addWorker(Runnable, boolean)方法进行拆解。

(1)检查任务队列是否在某些特定的条件下为空,代码如下所示。

// 检查队列是否在某些特定的条件下为空

if (rs >= SHUTDOWN &&

! (rs == SHUTDOWN &&

firstTask == null &&

! workQueue.isEmpty()))

return false;

(2)在通过步骤(1)的校验后,则进入内层for循环,在内层for循环中通过CAS来增加线程池中的线程数量,如果CAS操作成功,则直接退出双重for循环。如果CAS操作失败,则查看当前线程池的状态是否发生了变化,如果线程池的状态发生了变化,则通过continue关键字重新通过外层for循环校验任务队列,检验通过再次执行内层for循环的CAS操作。如果线程池的状态没有发生变化,此时上一次CAS操作失败了,则继续尝试CAS操作。代码如下所示。

for (;😉 {

//获取线程池中的线程数量

int wc = workerCountOf©;

//如果线程池中的线程数量超出限制,直接返回false

if (wc >= CAPACITY ||

wc >= (core ? corePoolSize : maximumPoolSize))

return false;

//通过CAS方式向线程池新增线程数量

if (compareAndIncrementWorkerCount©)

//通过CAS方式保证只有一个线程执行成功,跳出最外层循环

break retry;

//重新获取ctl的值

c = ctl.get();

//如果CAS操作失败了,则需要在内循环中重新尝试通过CAS新增线程数量

if (runStateOf© != rs)

continue retry;

}

(3)CAS操作成功后,表示向线程池中成功添加了工作线程,此时,还没有线程去执行任务。使用全局的独占锁mainLock来将新增的工作线程Worker对象安全的添加到workers中。

总体逻辑就是:创建新的Worker对象,并获取Worker对象中的执行线程,如果线程不为空,则获取独占锁,获取锁成功后,再次检查线线程的状态,这是避免在获取独占锁之前其他线程修改了线程池的状态,或者关闭了线程池。如果线程池关闭,则需要释放锁。否则将新增加的线程添加到工作集合中,释放锁并启动线程执行任务。将是否启动线程的标识设置为true。最后,判断线程是否启动,如果没有启动,则调用addWorkerFailed(Worker)方法。最终返回线程是否起送的标识。

//跳出最外层for循环,说明通过CAS新增线程数量成功

//此时创建新的工作线程

boolean workerStarted = false;

boolean workerAdded = false;

Worker w = null;

try {

//将执行的任务封装成worker

w = new Worker(firstTask);

final Thread t = w.thread;

if (t != null) {

//独占锁,保证操作workers时的同步

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

//此处需要重新检查线程池状态

//原因是在获得锁之前可能其他的线程改变了线程池的状态

int rs = runStateOf(ctl.get());

if (rs < SHUTDOWN ||

(rs == SHUTDOWN && firstTask == null)) {

if (t.isAlive())

throw new IllegalThreadStateException();

//向worker中添加新任务

workers.add(w);

int s = workers.size();

if (s > largestPoolSize)

largestPoolSize = s;

//将是否添加了新任务的标识设置为true

workerAdded = true;

}

} finally {

//释放独占锁

mainLock.unlock();

}

//添加新任成功,则启动线程执行任务

if (workerAdded) {

t.start();

//将任务是否已经启动的标识设置为true

workerStarted = true;

}

}

} finally {

//如果任务未启动或启动失败,则调用addWorkerFailed(Worker)方法

if (! workerStarted)

addWorkerFailed(w);

}

//返回是否启动任务的标识

return workerStarted;

addWorkerFailed(Worker)方法

在addWorker(Runnable, boolean)方法中,如果添加工作线程失败或者工作线程启动失败时,则会调用addWorkerFailed(Worker)方法,下面我们就来看看addWorkerFailed(Worker)方法的实现,如下所示。

private void addWorkerFailed(Worker w) {

//获取独占锁

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

//如果Worker任务不为空

if (w != null)

//将任务从workers集合中移除

workers.remove(w);

//通过CAS将任务数量减1

decrementWorkerCount();

tryTerminate();

} finally {

//释放锁

mainLock.unlock();

}

}

addWorkerFailed(Worker)方法的逻辑就比较简单了,获取独占锁,将任务从workers中移除,并且通过CAS将任务的数量减1,最后释放锁。

拒绝策略

我们在分析execute(Runnable)方法时,线程池会在适当的时候调用reject(Runnable)方法来执行相应的拒绝策略,我们看下reject(Runnable)方法的实现,如下所示。

final void reject(Runnable command) {

handler.rejectedExecution(command, this);

}

通过代码,我们发现调用的是handler的rejectedExecution方法,handler又是个什么鬼,我们继续跟进代码,如下所示。

private volatile RejectedExecutionHandler handler;

再看看RejectedExecutionHandler是个啥类型,如下所示。

package java.util.concurrent;

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(一)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(二)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Redis常见面试题汇总(300+题)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

[外链图片转存中…(img-LTFhqp76-1714538676607)]

Mysql面试题汇总(一)

[外链图片转存中…(img-i6M2qUD8-1714538676608)]

Mysql面试题汇总(二)

[外链图片转存中…(img-Gb187og7-1714538676608)]

Redis常见面试题汇总(300+题)

[外链图片转存中…(img-q8JuwmEb-1714538676608)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值