「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
runState:共5种状态,使用位移运算来标识线程池状态;从小到大依次是:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED,可以通过比较值的大小确定线程状态
其生命周期转换如下入所示:
ThreadPoolExecutor的运行机制
很多资料里都说ThreadPoolExecutor是一种生产者-消费者模型,但是《图解多线程设计模式》里面说它是一种Worker Thread模式。在Worker Thread模式中,工人线程(worker thread)会逐个取回工作并进行处理。当所有工作全部完成后,工人线程会等待新的工作到来。他们都有任务提交者,都有队列存储任务,也都有线程去处理任务。一开始我也是无法清除分辨这两者的异同,后边随着阅读调试源码,体会到两者的不同:缓冲队列和worker线程同属于一个组件,拥有相同的生命周期,两者是一体的,如果关闭线程池,工人线程会消亡,队列也会被回收,在生产者-消费者模型中,缓冲队列和消费者没有非常强的绑定关系,可以相互独立存在
生产者调用相关方法提交任务到阻塞队列进行缓冲或者直接交给工作线程,工作线程负责任务的执行,从而将调用与执行,提高响应速度,控制执行顺序。
站在不同角色的角度,就会有不同的问题:
1、生产者如何提交任务
检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其一般执行过程如下:
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常
特殊情况,corePooSize设置为0,第1步就跳过执行了
具体实现是调用execute方法来提交任务到线程池中:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1、如果比核心线程数少的线程正在运行,就启动一个新的线程,并把当前command直接作为
// 它的第一个任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2、如果workerCount >= corePoolSize,且线程池内的阻塞队列未满
// 则将任务添加到该阻塞队列中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次检查线程池状态,原因:
// 1、方法执行到此处,线程池可能被关闭,线程池不是运行状态,需要回滚入队的任务,执行
// 拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 2、线程池虽然状态为RUNNING,但可能自上次状态检查以后,活跃的线程消亡,如果是这种情况,需要增加工作线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3、如果任务不能入队,那就尝试新增一个工作线程。如果增加操作失败,有可能线程池被
// shut down或者工作线程饱和了,需要执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
通俗来讲,以寄快递为例,我们去一个快递网点寄快递,如果一个工人都没有,网点当然需要招人来处理快件,同时网点具有一定的空间(阻塞队列)可以暂存寄件人的快递。因为招快递员是一个耗时耗钱的过程(实例化线程),所以优先把快件堆放在网点中,让现有快递员慢慢把这些快件处理掉,如果产生了大量积压,以至于屋里都堆满了再也放不下,比较好的解决方式就是招一些临时工来处理这些快件。如果这样也无法解决,就只能告诉寄件的客户,我们处理不了了,你明天再来吧。
2、如何添加工人
先看一下工作线程(Worker)的结构:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
// 绑定的工作线程
final Thread thread;
// 初始化时要执行的任务,可能为空
Runnable firstTask;
// 每个线程的工作计数器
volatile long completedTasks;
Worker(Runnable firstTask) {
// 设置AQS中state字段为-1,表示禁止中断,直到runWoker方法执行
// -1:禁止中断;0:允许中断;1:执行任务,禁止中断
setState(-1);
// 第一个执行的任务
this.firstTask = firstTask;
// 通过线程工厂声明一个线程
this.thread = getThreadFactory().newThread(this);
}
}
通过调用addWorker方法,增加一个工作线程。
检查是否可以根据当前线程池的状态和线程个数边界来增加worker。如果可以,工人数量做相应调整,firstTask作为这个新启动线程的第一个任务。如果线程已经停止或者正在shut down,则返回false。如果线程创建过程中出现了问题,也会返回false。下面看具体代码:
/**
* firstTask:新线程应该执行的任务,根据该值是否为NULL来判断该Worker的用途,不为NULL说明用来处理新任务
* core:增加核心线程还是非核心线程,为true时用corePoolSize作为worker数量上界,为false时,用maximumPoolSize
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
// 当前线程池状态
int c = ctl.get();
int rs = runStateOf(c);
// SHUTDOWN状态的线程池不接受新任务,但是处理队列中的任务,这种情况需要增加worker用来处理存量任务,其他情况不能添加worker
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN &&
firstTask == null && !workQueue.isEmpty()))
return false;
for (;;) {
// 当前worker数量
int wc = workerCountOf(c);
// 大于等于最大worker数量或者大于等于worker边界值,不允许添加worker
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 修改worker数量,成功则终止外部循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 重新获取ctl
c = ctl.get();
// 线程池状态是否改变,如果改变,重试外部循环看是否能加入worker进而对Worke数量+1,否则重试内部循环尝试修改worker数量
if (runStateOf(c) != rs)
continue retry;
}
}
// 工人启动标志位
boolean workerStarted = false;
// 工人添加标志位
boolean workerAdded = false;
Worker w = null;
try {
// 实例化Worker
w = new Worker(firstTask);
// 获得与该Worker绑定的线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
// RUNNING或SHUTDONG状态没有新提交任务,且创建的线程没有运行时,可以新增线程
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 线程不能已经启动
if (t.isAlive())
throw new IllegalThreadStateException();
// private final HashSet<Worker> workers = new HashSet<Worker>();
workers.add(w);
int s = workers.size();
// largestPoolSize记录线程池大小的最大值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// worker已增加,启动线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 线程启动失败
if (! workerStarted)
// 回滚工人线程的创建过程:从工人集合中移除工人(如果创建了worker),工人数量-1
addWorkerFailed(w);
}
return workerStarted;
}
3、工作线程如何工作 Worker实现了Runnable接口,并且在线程工厂生成线程时被当做Runnable实例传给了新线程,所以在addWorker中启动线程时,就会调用Worker的run方法,但是run方法把这段逻辑委托给了外部的runWorker方法,下面来分析一下:
runWorker方法的执行流程:
- while循环不断地通过getTask()方法获取任务
- getTask()方法从阻塞队列中取任务
- 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态
- 执行任务
- 如果getTask结果为null则跳出循环,执行processWorkerExit()方法,销毁线程
final void runWorker(Worker w) {
// 当前工作线程
Thread wt = Thread.currentThread();
// 线程的初始任务
Runnable task = w.firstTask;
// 清理初始任务
w.firstTask = null;
// Worker中state:-1->0
// state = -1,表示禁止中断,w.unlock即state = 0,表示允许中断
w.unlock();
// 表示用户代码是否报出异常
boolean completedAbruptly = true;
try {
// 初始任务为空或者从队列中拿到任务
while (task != null || (task = getTask()) != null) {
// worker的state:0->1,获得锁
w.lock();
// 至少为STOP状态的线程池不接受新任务,也不执行队列中的任务,所以要中断当前worker thread
// 处于RUNNING、如果线程池当前状态是RUNNININ,则重置中断标志,重置后需要重新检查下线程池状态,因为当重置中断标志时候,可能调用了线程池的shutdown方法改变了线程池状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
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 {
// help gc
task = null;
// worker任务加1
w.completedTasks++;
// // worker的state:1->0,释放锁
w.unlock();
}
}
// 任务正常完成
completedAbruptly = false;
} finally {
// while循环打破,run方法将要执行完,线程即将销毁
processWorkerExit(w, completedAbruptly);
}
}