总结
以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!
下面给大家分享下我的面试大全资料
- 第一份是我的后端JAVA面试大全
后端JAVA面试大全
- 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理
MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
- 第三份是Spring全家桶资料
MySQL+Redis学习笔记算法+JVM+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);
}
}
}
这个方法的内部逻辑很符合之前对核心参数的解释,根据这个方法的执行逻辑可以推断出线程池创建线程的基本流程:
判断正在运行的任务数是否小于核心线程数,如果小于核心线程数那么直接创建一个核心线程运行这个任务。
如果目前正在运行的任务数大于核心线程数,那么尝试添加到阻塞队列中等待调度运行。
如果阻塞队列已满无法添加到阻塞队列中,那么尝试创建非核心线程运行,如果创建非核心线程失败(线程池容量已满),那么会触发拒绝策略。
以上代码中可以发现,不论是创建核心线程还是非核心线程都是通过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,学习视频)