概述
在前面的文章中,主要讲解了线程池的任务执行机制,顺带提了一下 ctl 变量的工作原理。
- 线程池源码-伟大而渺小的ctl
- 线程池源码-任务提交
- 线程池源码-任务执行
本文主要带大家了解一下,线程池都有哪些状态,以及这些状态之间是如何切换的。
线程池状态
RUNNING
运行状态,这个时候线程池就像一个年轻力壮的小伙子,能扛能打,既能接受新的任务,同时也会处理任务队列中已经堆积的任务,这也是线程池初始化的状态。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
SHUTDOWN
此时线程池有点像人到中年,身体已经在走下坡路,开始不接收新的任务了,但是依然会把手头已有的活给干完(把任务队列中已有的任务处理完毕),善始善终嘛。
STOP
此时线程池像受病魔困扰的病人,命都快保不住了还干活?不接收新的任务,手头在做的任务全部终止,「安心养病」,但是会交接工作(后面会讲到)。
TIDYING
此状态意味着线程池被「清场」了,此时的线程池空空如也,没有任务,没有工作线程。有点像公园晚上关门前会检查里面人是否都走光了。
TERMINATED
是紧随 TIDYING 的一个状态,执行完 terminated() 中的操作,就切换到这个状态,意味着线程池被正式关闭。这就好比检查完公园没人之后,保安把大门锁了(terminated 操作)。
状态转换
1.RUNNING -> SHUTDOWN
调用线程池的 shutdown() 方法,或者被隐式地调用 finalize() 方法。
2.(RUNNING or SHUTDOWN) -> STOP
调用线程池的 shutdownNow() 方法。
3.SHUTDOWN -> TIDYING
线程池任务队列为空,工作线程数等于零
4.STOP -> TIDYING
线程池工作线程数等于0
5.TIDYING -> TERMINATED
当 terminated() 方法被执行完毕,默认 terminated() 方法中什么都没做,是留给外部的扩展接口
状态转化图如下:
相关方法
上面讲了状态转化主要涉及到 shutdown() , shutdownNow() 两个方法,来看一下它们都做了什么操作,以及它们之间的区别。
shutdown()
此方法调用后除了不会接受新的任务以外,不会马上停止所有线程,会优先把那些空闲的线程干掉,而正在工作的线程则是把现有任务做完。
流程如下:
- 检查是否有终止线程池的权限,会挨个检查每个线程
- 修改线程池状态为 SHUTDOWN
- 终止空闲线程,怎么判断线程是否空闲,通过 tryLock() 尝试获取它的锁,如果成功获取,则证明其为空闲状态
- 调用 onShutdown() 方法,此方法默认没有实现,也是提供给外部的扩展接口
- 后续的终止操作
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 1.检查是否有终止线程池的权限
checkShutdownAccess();
// 2.修改线程池状态为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 3.终止空闲线程
interruptIdleWorkers();
// 4.调用 hook 方法
onShutdown();
} finally {
mainLock.unlock();
}
// 进行后续的终止操作
tryTerminate();
}
tryTerminate()
我们来看一下后续的终止操作做了什么。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 检查是否符合终止操作执行的条件
// 1.RUNNING 状态的线程池不能 Terminate
// 2.TIDYING,TERMINATED 状态的线程池不能 Terminate
// 3.SHUTDOWN 状态,但是任务队列不为空的线程池不能 Terminate
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 再尝试去中断空闲线程,直到工作线程数量为 0
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 修改线程池状态为 TIDYING,并将线程数量设为 0
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 调用 terminated() 实现方法
terminated();
} finally {
// 紧接着就将线程池状态改为 TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 通知那些等待终止信号的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
shutdownNow()
和 shutdown() 的操作大致相同,区别在于:
- shutdown() 将线程池状态改为 SHUTDOWN;shutdownNow() 将线程池状态改为 STOP
- shutdown() 不会中断正在工作的线程,调用的是 interruptIdleWorkers() 方法,终止空闲线程;shutdownNow() 则是中断所有线程,调用的是 interruptWorkers() 方法。
- shutdownNow() 会把没执行完的任务收集并返回
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
// 尝试中断所有线程
interruptWorkers();
// 将没执行完的任务收集返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 后续终止操作
tryTerminate();
return tasks;
}
总结
通过这篇文章,我们了解了:
- 线程池的状态,以及它们之间转化的方式
- shutdown(), shutdownNow() 方法的操作流程,以及它们之间的区别
如果觉得文章对你有帮助,欢迎留言点赞。
最后
以上就是俏皮薯片为你收集整理的线程池源码-线程池状态的全部内容,希望文章能够帮你解决线程池源码-线程池状态所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复