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

分享

这次面试我也做了一些总结,确实还有很多要学的东西。相关面试题也做了整理,可以分享给大家,了解一下面试真题,想进大厂的或者想跳槽的小伙伴不妨好好利用时间来学习。学习的脚步一定不能停止!

薪酬缩水,“裸辞”奋战25天三面美团,交叉面却被吊打,我太难了

Spring Cloud实战

薪酬缩水,“裸辞”奋战25天三面美团,交叉面却被吊打,我太难了

Spring Boot实战

薪酬缩水,“裸辞”奋战25天三面美团,交叉面却被吊打,我太难了

面试题整理(性能优化+微服务+并发编程+开源框架+分布式)

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

需要这份系统化的资料的朋友,可以点击这里获取

if (workerCountOf© < corePoolSize) {

//重新开启线程执行任务

if (addWorker(command, true))

return;

c = ctl.get();

}

//如果线程池处于RUNNING状态,则将任务添加到阻塞队列中

if (isRunning© && workQueue.offer(command)) {

//再次获取线程池的状态和线程池中线程的数量,用于二次检查

int recheck = ctl.get();

//如果线程池没有未处于RUNNING状态,从队列中删除任务

if (! isRunning(recheck) && remove(command))

//执行拒绝策略

reject(command);

//如果线程池为空,则向线程池中添加一个线程

else if (workerCountOf(recheck) == 0)

addWorker(null, false);

}

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

else if (!addWorker(command, false))

reject(command);

}

整个任务的执行流程,我们可以简化成下图所示。

接下来,我们拆解execute(Runnable)方法,具体分析execute(Runnable)方法的执行逻辑。

(1)线程池中的线程数是否小于corePoolSize核心线程数,如果小于corePoolSize核心线程数,则向workers工作线程集合中添加一个核心线程执行任务。代码如下所示。

//线程池中的线程数量小于corePoolSize的值

if (workerCountOf© < corePoolSize) {

//重新开启线程执行任务

if (addWorker(command, true))

return;

c = ctl.get();

}

(2)如果线程池中的线程数量大于corePoolSize核心线程数,则判断当前线程池是否处于RUNNING状态,如果处于RUNNING状态,则添加任务到待执行的任务队列中。注意:这里向任务队列添加任务时,需要判断线程池是否处于RUNNING状态,只有线程池处于RUNNING状态时,才能向任务队列添加新任务。否则,会执行拒绝策略。代码如下所示。

if (isRunning© && workQueue.offer(command))

(3)向任务队列中添加任务成功,由于其他线程可能会修改线程池的状态,所以这里需要对线程池进行二次检查,如果当前线程池的状态不再是RUNNING状态,则需要将添加的任务从任务队列中移除,执行后续的拒绝策略。如果当前线程池仍然处于RUNNING状态,则判断线程池是否为空,如果线程池中不存在任何线程,则新建一个线程添加到线程池中,如下所示。

//再次获取线程池的状态和线程池中线程的数量,用于二次检查

int recheck = ctl.get();

//如果线程池没有未处于RUNNING状态,从队列中删除任务

if (! isRunning(recheck) && remove(command))

//执行拒绝策略

reject(command);

//如果线程池为空,则向线程池中添加一个线程

else if (workerCountOf(recheck) == 0)

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;

最后

ActiveMQ消息中间件面试专题

  • 什么是ActiveMQ?
  • ActiveMQ服务器宕机怎么办?
  • 丢消息怎么办?
  • 持久化消息非常慢怎么办?
  • 消息的不均匀消费怎么办?
  • 死信队列怎么办?
  • ActiveMQ中的消息重发时间间隔和重发次数吗?

ActiveMQ消息中间件面试专题解析拓展:

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


redis面试专题及答案

  • 支持一致性哈希的客户端有哪些?
  • Redis与其他key-value存储有什么不同?
  • Redis的内存占用情况怎么样?
  • 都有哪些办法可以降低Redis的内存使用情况呢?
  • 查看Redis使用情况及状态信息用什么命令?
  • Redis的内存用完了会发生什么?
  • Redis是单线程的,如何提高多核CPU的利用率?

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


Spring面试专题及答案

  • 谈谈你对 Spring 的理解
  • Spring 有哪些优点?
  • Spring 中的设计模式
  • 怎样开启注解装配以及常用注解
  • 简单介绍下 Spring bean 的生命周期

Spring面试答案解析拓展

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


高并发多线程面试专题

  • 现在有线程 T1、T2 和 T3。你如何确保 T2 线程在 T1 之后执行,并且 T3 线程在 T2 之后执行?
  • Java 中新的 Lock 接口相对于同步代码块(synchronized block)有什么优势?如果让你实现一个高性能缓存,支持并发读取和单一写入,你如何保证数据完整性。
  • Java 中 wait 和 sleep 方法有什么区别?
  • 如何在 Java 中实现一个阻塞队列?
  • 如何在 Java 中编写代码解决生产者消费者问题?
  • 写一段死锁代码。你在 Java 中如何解决死锁?

高并发多线程面试解析与拓展

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM


jvm面试专题与解析

  • JVM 由哪些部分组成?
  • JVM 内存划分?
  • Java 的内存模型?
  • 引用的分类?
  • GC什么时候开始?

JVM面试专题解析与拓展!

BAT面试文档:ActiveMQ+redis+Spring+高并发多线程+JVM

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

需要这份系统化的资料的朋友,可以点击这里获取

性能缓存,支持并发读取和单一写入,你如何保证数据完整性。

  • Java 中 wait 和 sleep 方法有什么区别?
  • 如何在 Java 中实现一个阻塞队列?
  • 如何在 Java 中编写代码解决生产者消费者问题?
  • 写一段死锁代码。你在 Java 中如何解决死锁?

高并发多线程面试解析与拓展

[外链图片转存中…(img-c2yuONJw-1715489990306)]


jvm面试专题与解析

  • JVM 由哪些部分组成?
  • JVM 内存划分?
  • Java 的内存模型?
  • 引用的分类?
  • GC什么时候开始?

JVM面试专题解析与拓展!

[外链图片转存中…(img-ljv0bChy-1715489990306)]

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

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值