本文将深入探讨ThreadPoolExecutor线程池的工作原理和源码实现。通过了解其内部机制,我们可以更好地利用线程池来提高程序的性能和并发能力。
回顾
程池解决的问题:减少线程创建销毁的开销,重复利用。便于监控管理。
线程池的核心参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数 == 核心线程数 + 非核心线程数
- keepAliveTime:最大空闲时间,默认是非核心线程的
- unit:时间单位
- workQueue:存储任务的
- threadFactory:构建thread对象的
- handler:拒绝策略
线程池的核心属性:int类型的属性ctl,维护线程池状态和工作线程数
线程池的状态:
- RUNNING:可以接收新任务,工作队列中排队的任务正常处理
- SHUTDOWN:不能接收新任务,工作队列中排队的任务正常处理
- STOP:不能接收新任务,工作队列中的任务会被移除作为返回结果
线程池的执行流程:
一、addWorker源码流程
在线程池构建工作线程时:
- 判断:
- 判断线程池状态
- 判断工作线程个数
- 创建并启动
- 构建工作线程
- 启动工作线程
// 创建工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
// ===================判断状态,工作线程个数=====================
retry:
for (;;) {
// 拿到核心属性ctl
int c = ctl.get();
// 从ctl中获取线程池状态
int rs = runStateOf(c);
// rs >= SHUTDOWN:满足这个判断,代表线程池状态不是RUNNING
if (rs >= SHUTDOWN &&
// 状态是SHUTDOWN,没传递任务,同时工作队列中有任务
// 如果满足这个情况,不进入if。
! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
// 从ctl中获取工作线程个数
int wc = workerCountOf(c);
// wc >= CAPACITY:健壮性判断,工作线程个数,不能超过(1<<29) - 1
if (wc >= CAPACITY ||
// 判断工作线程是否满足核心参数的设置。
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 可以尝试创建工作线程。
// 利用CAS的原子性,将ctl + 1,如果成功,直接跳出外层循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 有并发情况,修改ctl失败。
// 重新获取ctl
c = ctl.get();
// 基于重新获取到的ctl拿到线程池状态,比对一下和之前状态是否一致
if (runStateOf(c) != rs)
// 说明线程池状态改变了。重走外层for循环
continue retry;
}
}
// ===================创建工作线程,启动工作线程=====================
// ===================创建工作线程,本质就是new一个Worker对象,扔到HashSet里=====================
// 工作线程启动了咩:没~
// 工作线程创建了咩:没~
boolean workerStarted = false;
boolean workerAdded = false;
// Worker就是工作线程
Worker w = null;
try {
// new工作线程!同时也构建了thread对象,传入了任务
w = new Worker(firstTask);
// 拿到Worker对象中声明好的thread对象
final Thread t = w.thread;
// 健壮性判断
if (t != null) {
// 获取到线程池里面的锁
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// 线程池状态
int rs = runStateOf(ctl.get());
// 再次判断线程池状态能否构建。
if (rs == RUNNING ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果线程已经start了,那就扔异常!
if (t.isAlive())
throw new IllegalThreadStateException();
// 将工作线程添加到HashSet集合中
workers.add(w);
// 获取工作线程个数
int s = workers.size();
// largestPoolSize:记录工作线程的历史最大值。
if (s > largestPoolSize)
largestPoolSize = s;
// 工作线程创建成功。
workerAdded = true;
}
} finally {
// 释放锁资源
mainLock.unlock();
}
// 如果创建工作线程成功
if (workerAdded) {
// 启动工作线程
t.start();
// 启动成功!
workerStarted = true;
}
}
}
return workerStarted;
}
Worker(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
thread.start();
thread.start();
二、Worker对象的封装
addWorker里,线程启动后,执行了t.start();
线程启动要执行run方法,run方法干嘛了???
发现线程启动后,执行的是Worker中的run方法。
Worker中的run方法,执行了runWorker方法。
// 简略版的Worker,核心内容都在。
private final class Worker implements Runnable {
final Thread thread = new Thread(this);
Runnable firstTask;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
// thread.start后,执行的是这个run方法。
public void run() {
// 想看工作线程的后续处理,要看runWorker方法了
runWorker(this);
}
// 省略部分代码
}
三、runWorker执行任务流程
在runWorker方法中,获取Worker对象自带的任务,如果任务不为null,执行当前任务,还提供了前后两个勾子函数,供咱们程序员自行实现。任务执行完,将引用置位null,同时记录任务完成数 + 1
// 线程启动后,执行的runWorker方法
// 省略了部分中断内容
final void runWorker(Worker w) {
// 拿到当前工作线程
Thread wt = Thread.currentThread();、
// 拿到new Worker时,传递过来的任务
Runnable task = w.firstTask;
// help gc
w.firstTask = null;
try {
// 拿到的任务,不为null,直接执行
// 如果拿到的任务已经处理完了,就通过getTask拿。
while (task != null || (task = getTask()) != null) {
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
task = null;
// 当前线程处理任务个数 + 1
w.completedTasks++;
}
}
}
}
四、getTask获取任务流程
工作线程执行完一个任务后,会执行getTask方法。
getTask方法会再次获取一个任务,然后执行任务。
getTask其实就是去工作队列中拉取任务。
核心线程基本就是通过take拉取任务,没任务就死等。
非核心线程就是基于poll拉取任务,等待keepAliveTime时间,没任务就告辞。
// 工作线程来收任务啦!!
private Runnable getTask() {
// 超时了没???没呢!
boolean timedOut = false;
// 死循环~~~
for (;;) {
// 再次获取ctl,拿到线程池状态
int c = ctl.get();
int rs = runStateOf(c);
// 线程池状态为STOP,直接进if
// 线程池状态为SHUTDOWN,并且工作队列为空,直接进入if
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 进来代表当前线程可以凉凉了!
// 对ctl - 1(保证原子性。)
decrementWorkerCount();
// 返回null
return null;
}
// 拿到工作线程个数
int wc = workerCountOf(c);
// 如果有非核心线程,timed为true。(不考虑核心线程超时问题。)
boolean timed = wc > corePoolSize;
// wc > maximumPoolSize正常人类的操作不会发生。
// 第一次,timedOut肯定是false。
if ( (timed && timedOut)
&& (wc > 1 || workQueue.isEmpty())) {
// 第一次进不来!
// 到这,也没必要去工作队列拉取任务了。
// 对ctl - 1,当前线程可以拜拜了!
if (compareAndDecrementWorkerCount(c))
return null;
// 如果CAS失败了,重新走状态判断,和工作线程数判断
continue;
}
try {
// 如果有非核心线程,
// 执行工作队列的poll方法,将线程挂起keepAliveTime时间,时间到了,没任务,获取到null
// 如果timed为false,核心线程直接take在工作队列的位置,死等任务,没任务就挂起等着,一直等到有任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 如果拿到任务,直接返回任务
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
五、shutdown关闭线程池流程
线程池使用完毕后,一定要手动的关闭线程池,不然工作线程无法被回收。
工作线程无法被回收,会导致Worker对象不会被回收。
Worker对象是线程池的内部类,内部类无法回收,外部类自然无法被回收。
线程池不用了之后,记得执行shutdown,shutdown内部会将线程池的状态修改为SHUTDOWN状态。
同时shutdown方法会中断工作线程
线程池内部多个地方都判断了线程池状态
- 获取任务的getTask方法会返回null。
- 这样runWorker方法中的while循环会退出。
- while循环退出,runWorker方法结束
- runWorker方法结束,run方法结束
- run方法结束,工作线程凉凉~~~