ThreadPoolExecutor 优雅关闭线程池的原理.md
经典关闭线程池代码
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.shutdown();
while (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("线程池中还有任务在处理");
}
shutdown 做了什么?
先上源码
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 线程安全性检查
checkShutdownAccess();
// 更新线程池状态为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 尝试关闭空闲线程
interruptIdleWorkers();
// 空实现
onShutdown();
} finally {
mainLock.unlock();
}
// 尝试中止线程池
tryTerminate();
}
每个方法都有特定的目的,其中 checkShutdownAccess() 和 advanceRunState(SHUTDOWN)比较简单,所以这里不再描述了,而 interruptIdleWorkers() 和 tryTerminate()。
interruptIdleWorkers 做了什么?
关闭当前空闲线程。
onlyOne = true:至多关闭一个空闲worker,可能关闭0个。
onlyOne = false:遍历所有的worker,只要是空闲的worker就关闭。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 尝试获得锁,如果获得锁则表明该worker是空闲状态
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
w.tryLock():该方法并不会阻塞,尝试一次如果不成功就返回false,成功则放回true。
在方法 runWorker 中一旦work获得了任务,就会调用调用了 w.lock(),从而倘若 worker 是无锁状态,就是空闲状态。
因此 Worker 之所以继承 AbstractQueuedSynchronizer 实际上是为了达到用无锁,有锁标识worker空闲态与忙碌态,从而方便控制worker的销毁操作。
tryTerminate 做了什么?
这个只是尝试将线程池的状态置为 TERMINATE 态,如果还有worker在执行,则尝试关闭一个worker。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) || // 是运行态
runStateAtLeast(c, TIDYING) || // 是 TIDYING 或者 TERMINATE 态
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) // SHUTDOWN 态且队列中有任务
return;
if (workerCountOf(c) != 0) {
// 还存在 worker,则尝试关闭一个worker
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 通过CAS,先置为 TIDYING 态
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
// TIDYING 态
try {
// 空方法
terminated();
} finally {
// 最终更新为 TERMINATED 态
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
通过如下的方法简单做一些过滤。因为状态是从 TIDYING 态往后才到 TERMINATE 态。从这个过滤条件,可以看出如果是STOP态也是会通过的。不过如果线程池到了STOP应该就不会再使用了吧,所以也是不会有什么影响。
if (isRunning(c) || // 是运行态
runStateAtLeast(c, TIDYING) || // 是 TIDYING 或者 TERMINATE 态
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) // SHUTDOWN 态且队列中有任务
return;
如果该线程池中有worker,则中止1个worker。通过上面的过滤条件,到了这一步,那么肯定是 SHUTDOWN 态(忽略 STOP 态),并且任务队列已经被处理完成。
if (workerCountOf(c) != 0) {
// 还存在 worker,则尝试关闭一个worker
interruptIdleWorkers(ONLY_ONE);
return;
}
如果该线程池中有多个worker,终止1个worker之后 tryTerminate() 方法就返回了,那么剩下的worker在哪里被处理的呢?(看后面的解答)
假设线程池中的worker都已经关闭并且队列中也没有任务,那么后面的代码将会将线程池状态置为 TERMINATE 态。terminate() 是空实现,用于有需要的自己实现处理,线程池关闭之后的逻辑。
awaitTermination 做了什么
这个方法只是判断当前线程池是否为 TERMINATED 态,如果不是则睡眠指定的时间,如果睡眠中途线程池变为终止态则会被唤醒。这个方法并不会处理线程池的状态变更的操作,纯粹是做状态的判断,所以得要在循环里边做判断。
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED)) // 是否为 TERMINATED 态
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos); // native 方法,睡一觉
}
} finally {
mainLock.unlock();
}
}
问题
从 shutdown() 方法源码来看有很大概率没有完全线程池,而awaitTermination() 方法则只是判断线程池状态,并没有关闭线程池状态,那么剩下的worker什么时候促发关闭呢?关键代码逻辑在一个worker被关闭之后,触发了哪些事情。
runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// 省略其他不需要的代码
try{
task.run();
} finally {
task = null;
w.unlock();
}
}
// 用于标识是否是task.run报错
completedAbruptly = false;
} finally {
// 线程关闭的时候调用
processWorkerExit(w, completedAbruptly);
}
}
completedAbruptly:用于标识是否是task.run报错,如果值为 TRUE,则是task.run 异常;反之,则没有发生异常
从上面的核心代码来看,当一个worker被关闭之后会调用 processWorkerExit() 方法。看看它做了什么。
processWorkerExit 做了什么
对一个worker退出之后做善后工作,比如统计完成任务数,将线程池的关闭态传播下去,根据条件补充 worker。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 如果有worker则尝试关闭一个,否则置为TERMINATE态
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
线程池关闭的关键就在于一个worker退出之后,会调用 tryTerminate() 方法,将退出的信号传递下去,这样其他的线程才能够被依次处理,最后线程池会变为 TERMINATE 态。
ThreadPoolExecutor 优雅关闭线程池的原理.md的更多相关文章
- 使用RunTime.getRunTime().addShutdownHook优雅关闭线程池
有时候我们用到的程序不一定总是在JVM里面驻守,可能调用完就不用了,释放资源. RunTime.getRunTime().addShutdownHook的作用就是在JVM销毁前执行的一个线程.当然这个 ...
- Java线程池实现原理与技术(ThreadPoolExecutor、Executors)
本文将通过实现一个简易的线程池理解线程池的原理,以及介绍JDK中自带的线程池ThreadPoolExecutor和Executor框架. 1.无限制线程的缺陷 多线程的软件设计方法确实可以最大限度地发 ...
- 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理
摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...
- juc线程池原理(三):ThreadFactory、拒绝策略、提交任务、关闭线程池
概要 (一) ThreadFactory 线程池中的ThreadFactory是一个线程工厂,线程池创建线程都是通过线程工厂对象(threadFactory)来完成的. 类图如下: 上面所说的thre ...
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)
在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...
- 深入浅出JAVA线程池使用原理1
前言: Java中的线程池是并发框架中运用最多的,几乎所有需要异步或并发执行任务的程序都可以使用线程池,线程池主要有三个好处: 1.降低资源消耗:可以重复使用已经创建的线程降低线程创建和销毁带来的消耗 ...
- Java并发(二十一):线程池实现原理
一.总览 线程池类ThreadPoolExecutor的相关类需要先了解: (图片来自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8% ...
- JUC回顾之-线程池的原理和使用
Java并发编程:线程池的使用 Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程 ...
- 11 java 线程池 实现原理
一 关键类的实现 1 ThreadPoolExecutor类 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的 ...
随机推荐
- 在Python中,如何用一行代码去判定整数二进制中的连续 1
利用字节位操作如何判断一个整数的二进制是否含有至少两个连续的1 的方法有多种,大家第一反应应该想到的是以下的第一种方法. 方法一:从头到尾遍历一遍每一位即可找出是否有连续的1存在 这个方法是最普遍的. ...
- 5.Shell 流程控制语句
1.流程控制语句 通过if.for.while.case这4种流程控制语句来学习编写难度更大.功能更强的Shell脚本 4.3.1 if条件测试语句: if条件测试语句可以让脚本根据实际情况自动执行相 ...
- 下载好的vue项目如何在自己电脑环境上运行,步骤!!
本文链接:https://blog.csdn.net/qq_39309900/article/details/84837659首先第一步,需要安装node.js 下载地址:https://nodejs ...
- 热门前沿知识相关面试问题-android插件化面试问题讲解
插件化由来: 65536/64K[技术层面上]随着代码越来越大,业务逻辑越来繁杂,所以很容易达到一个65536的天花板,其65536指的是整个项目中的方法总数如果达到这个数量时则不无法创建新的方法了, ...
- 关于TAILQ链表节点删除问题
这两天偶遇无线驱动中对链表节点删除的问题,刚开始修改代码的时候并没有很在意,把TAILQ链表当成一般的链表来处理,虽然修改以后没有出现段错误,但是后面review代码的时候发现,这样改不对.后面花了点 ...
- python+Appium自动化:Appium元素检测
appium模拟用户的真实操作,如果用户第一次进入app或许会弹出一些更新提示,或者是引导页面,但是下一次开启app时则没有引导页这些界面,这时,脚本中又肯定不考虑用两套代码来进行维护,此时如何应对这 ...
- sql关联查询更新速度慢的问题
原语句 update B b set b.fid = (select f.id from F f where f.bid = b.id) ; 可以考虑用 begin for f in (select ...
- Zabbix Server设置主机监控
- 简易MySQL存储过程
自从那天灵感突现,搜了下MySQL存储过程的实现,我就再也不会为造测试数据这种事情烦恼了,存储过程用起来简直太方便了. DROP PROCEDURE IF EXISTS insert2pay; DEL ...
- 题解 POJ1149 Pigs
先翻译一下吧(题面可以在原OJ上找) Mirko在一个由M个锁着的猪舍组成的养猪场工作,Mirko无法解锁任何猪舍,因为他没有钥匙.客户纷纷来到农场.他们每个人都有一些猪舍的钥匙,并想购买一定数量的猪 ...