2024年最新Java线程池(二)—— 实现原理,SpringBoot集成Redis集群

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

image.png

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

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

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

二、Java线程池原理

要说Java线程池的原理首先要明白Java线程池是做什么用的,可以将Java线程池理解为一个线程管理的API,线程池与Kotlin中的协程其实有部分功能是非常相似的,只不过Java的线程池无法实现自动切换线程罢了,它的核心功能是管理线程、复用线程,降低因频繁创建线程而带来的性能损耗。所以在分析线程池的原理时重点应该放在线程池是如何管理线程、复用线程上面。

2.1 初始化

先从初始化流程入手,这里使用JDK中CacheThreadPool为例,它调用的初始化方法是这样的:

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}

可以看到其实底层实现类是ThreadPool,跟进代码,最终调用的构造器如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
// 可重入锁,线程池内部保证了线程安全,由可重入锁实现
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
// 检查参数是否合法
if (corePoolSize >= 0 && maximumPoolSize > 0 && maximumPoolSize >= corePoolSize && keepAliveTime >= 0L) {
// 检查参数是否合法
if (workQueue != null && threadFactory != null && handler != null) {
// 初始化线程池核心变量
this.corePoolSize = corePoolSize; // 核心池大小
this.maximumPoolSize = maximumPoolSize; // 线程池最大容量
this.workQueue = workQueue; // 阻塞队列
this.keepAliveTime = unit.toNanos(keepAliveTime); // 非核心线程存活时间
this.threadFactory = threadFactory; // 线程工厂
this.handler = handler; // 拒绝策略
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}

都是熟悉的参数,线程池的构造器实现还是很简单的,可以看到就是初始化了一些核心参数,没有什么逻辑操作,初始化流程结束了,只要熟悉线程池的核心参数,这个过程看起来就很简单了。

2.2 运行任务

运行任务是分析线程池原理最重要的部分,使用线程池就是要让它执行任务,所以线程池的所有核心逻辑代码的起点就是用户提交一个任务,用户提交任务最简单的代码如下:

ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
// 要执行的逻辑代码
});

提交任务是通过ThreadPool的execute()方法发起的,先来看看这个方法的逻辑:

public void execute(Runnable command) {
// 参数检查
if (command == null) {
throw new NullPointerException();
} else {
int c = this.ctl.get();
if (workerCountOf© < this.corePoolSize) {
// 当前正在运行的线程数小于核心大小,直接创建核心线程运行该任务
if (this.addWorker(command, true)) {
return;
}

c = this.ctl.get();
}

// 尝试添加到阻塞队列中
if (isRunning© && this.workQueue.offer(command)) {
int recheck = this.ctl.get();
if (!isRunning(recheck) && this.remove(command)) {
this.reject(command);
} else if (workerCountOf(recheck) == 0) {
this.addWorker((Runnable)null, false);
}
} else if (!this.addWorker(command, false)) { // 尝试创建非核心线程运行
// 无法创建非核心线程,线程池已满,触发拒绝策略
this.reject(command);
}

}
}

这个方法的内部逻辑很符合之前对核心参数的解释,根据这个方法的执行逻辑可以推断出线程池创建线程的基本流程:

  1. 判断正在运行的任务数是否小于核心线程数,如果小于核心线程数那么直接创建一个核心线程运行这个任务。

  2. 如果目前正在运行的任务数大于核心线程数,那么尝试添加到阻塞队列中等待调度运行。

  3. 如果阻塞队列已满无法添加到阻塞队列中,那么尝试创建非核心线程运行,如果创建非核心线程失败(线程池容量已满),那么会触发拒绝策略。

以上代码中可以发现,不论是创建核心线程还是非核心线程都是通过Thread.addWorker(Runnable,boolean)方法实现的:

private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;😉 {
// 记录了线程池中运行的线程数量和线程的状态,高3位为状态,低29位为线程数
int c = ctl.get();
// 线程运行状态
int rs = runStateOf©;

// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;

for (;😉 {
// 线程数
int wc = workerCountOf©;
// 检查是否超出线程池的容量
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 将记录线程数的变量+1
if (compareAndIncrementWorkerCount©)
break retry;
c = ctl.get(); // Re-read ctl
// 如果没有成功创建,那么将会重试
if (runStateOf© != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}

// 初始化线程状态
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
/*

  • 创建一个新的线程,此处worker实际是线程池
  • 为了方便调度/获取线程状态而创建的封装类,
  • 其实worker可以认为是一个线程。这里的线程
  • 是Worker内部实例化时自动创建的。
    */
    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 rs = runStateOf(ctl.get());

// 状态检查
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();

// 添加存储Worker的集合中方便后续管理
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;

最后

我还为大家准备了一套体系化的架构师学习资料包以及BAT面试资料,供大家参考及学习

已经将知识体系整理好(源码,笔记,PPT,学习视频)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

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

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

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

  • 30
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值