关闭线程池方法一共有两个:void shutdown() 关闭线程池,继续执行完未完成的任务,不再接收新任务,我来为大家讲解一下关于java优雅关闭线程池,Java线程池核心八?跟着小编一起来看一看吧!
java优雅关闭线程池,Java线程池核心八
- 难度:中级
- 开发语言:Java
- 学习时间:30分钟
关闭线程池方法一共有两个:
- void SHUTDOWN()
- List<Runnable> shutdownNow()
void shutdown() 关闭线程池,继续执行完未完成的任务,不再接收新任务。
List<Runnable> shutdownNow() 立即关闭线程池,返回未执行的任务,不再接收新任务。
2.void shutdown() 方法源码分析void shutdown() 方法源码:
/**
* 关闭线程池,继续执行完未完成的任务,不再接收新任务。
*
* @throws SecurityException 当线程没有权限时,抛出此异常。
*/
public void shutdown() {
// 获取线程池同步锁
final Reentrantlock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// 检查当前调用者有没有关闭线程的权限,如果没有,将抛SecurityException异常。
checkShutdownAccess();
// 设置线程池状态
出
// 中断空闲线程
interruptIdleWorkers();
// ScheduledThreadPoolExecutor的hook
// hook的意思是:在某个时刻,hook方法(例如onShutdown()方法)会被调用,如果有需要执行的逻辑,那么请写在hook方法里。
// 例如,线程池在关闭时,会调用onShutdown()方法,如果本类或子类在关闭线程池的时候需要做点什么,那么就请写在onShutdown()方法中。
onShutdown();
} finally {
// 解锁
mainLock.unlock();
}
// 尝试关闭线程池
tryTerminate();
}
通过源码梳理出以下 6 个步骤:
- 获取线程池同步锁并加锁。
- 检查当前调用者有没有关闭线程的权限。
- 设置线程池状态为 SHUTDOWN。
- 中断所有空闲线程。
- 在 onShutdown() 方法中执行善后工作。
- 尝试关闭线程池。
比较重要的 3 步:
- 设置线程池状态为 SHUTDOWN。
- 中断所有空闲线程。
- 尝试关闭线程池。
以上 3 步对应的方法:
- advanceRunState(SHUTDOWN);
- interruptIdleWorkers();
- tryTerminate();
interruptIdleWorkers() 方法在第五章第 2 小节分析过。
advanceRunState(int targetState) 方法是在如果想把线程池状态设置为 SHUTDOWN 或 STOP 时使用。
tryTerminate() 方法是在如果想把线程池状态设置为 TIDYING 或 TERMINATED 时使用。
3.void advanceRunState(int targetState) 方法源码分析void advanceRunState(int targetState) 方法源码:
/**
* 设置线程池状态。
*
* @param targetState 目标状态一般为 SHUTDOWN 或 STOP。
*/
private void advanceRunState(int targetState) {
// 无限循环,直至线程池状态设置成功
for (;;) {
// 获取当前线程池状态
int c = ctl.get();
// runStateAtLeast(c, targetState):当前线程池状态等于或大于目标线程池状态
// ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))):使用CAS算法更新线程池状态
// CAS算法是一种原子操作。作用是:在并发条件下,不会发生原子性问题。
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
从源码中可以看出,方法内部使用无限循环也一定要把线程池状态设置成功。
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))
该行代码使用了 CAS 算法,关于什么是 CAS 算法可以看看以下三章:
“全栈2019”Java原子操作第一章:内存可见性volatile关键字详解
“全栈2019”Java原子操作第二章:i 是原子操作吗?何为原子性
“全栈2019”Java原子操作第三章:比较并交换CAS技术详解
一般需要将线程池状态设置为 SHUTDOWN 或 STOP 才使用 void advanceRunState(int targetState) 方法。
4.void tryTerminate() 方法源码分析void tryTerminate() 方法源码:
/**
* 尝试关闭线程池。一般将线程池状态设置为 TIDYING 或 TERMINATED。
*/
final void tryTerminate() {
// 无限循环
for (;;) {
// 获取线程池状态
int c = ctl.get();
// isRunning(c):当前线程池状态是否 < SHUTDOWN,即 RUNNING。
// runStateAtLeast(c, TIDYING):当前线程池状态是否 >= TIDYING,即 TIDYING 或 TERMINATED。
// (runStateLessThan(c, STOP):当前线程池状态是否 < STOP,即 RUNNING 或 SHUTDOWN。
// ! workQueue.isEmpty():任务队列不是空的。
// 综上所述,满足以下其中任何一点就结束方法:
// 1.当前线程池状态是 RUNNING。
// 2.当前线程池状态是 TIDYING 或 TERMINATED。
// 3.当前线程池状态是 RUNNING 或 SHUTDOWN 且 任务队列不为空。
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
// 结束方法
return;
// 当当前线程数不为0时
if (workerCountOf(c) != 0) {
// 中断一个空闲线程
interruptIdleWorkers(ONLY_ONE);
// 结束方法
return;
}
// 执行以下步骤的时候说明线程池中的线程数为0。
// 获取线程池同步锁
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// 将线程池状态设为 TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 执行线程池关闭前的善后操作
terminated();
} finally {
// 将线程池状态设为 TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 唤醒所有在此Condition上等等的线程。
// 实际上是唤醒调用awaitTermination(long timeout, TimeUnit unit)方法的线程
termination.signalAll();
}
return;
}
} finally {
// 解锁
mainLock.unlock();
}
}
}
一般需要将线程池状态设置为 TIDYING 或 TERMINATED 才使用 void tryTerminate() 方法。
该方法里面最重要的一个步骤就是只要线程池中还有线程,那么就等这些线程执行完任务空闲下来,然后中断它们,直至线程池中的线程数为 0:
// 当当前线程数不为0时
if (workerCountOf(c) != 0) {
// 中断一个空闲线程
interruptIdleWorkers(ONLY_ONE);
// 结束方法
return;
}
经过上面分析源码后,梳理使用 void shutdown() 方法关闭线程池关键点:
先调用 void advanceRunState(int targetState) 方法将线程池状态设置为 SHUTDOWN 或 STOP,这一步是为了不再接收新提交的任务,也不再添加新的线程。
再调用 void interruptIdleWorkers() 方法中断所有空闲线程,这一步是在逐步清除线程池中的线程,先把空闲的线程先清了。
最后调用 void tryTerminate() 方法是尝试去彻底关闭线程池,为什么是尝试呢?是因为还有任务没有执行完成,那么就不能关闭线程池。所以只要线程池中还有线程,那么就等这些线程执行完任务空闲下来,然后中断它们,直至线程池中的线程数为 0。当线程池中没有线程了,线程池也就彻底关闭了。
总结- 使用 void shutdown() 方法关闭线程池的 6 个步骤:
- 获取线程池同步锁并加锁。
- 检查当前调用者有没有关闭线程的权限。
- 设置线程池状态为 SHUTDOWN。
- 中断所有空闲线程。
- 在 onShutdown() 方法中执行善后工作。
- 尝试关闭线程池。
- 调用 void advanceRunState(int targetState) 方法将线程池状态设置为 SHUTDOWN 或 STOP,这一步是为了不再接收新提交的任务,也不再添加新的线程。
- 调用 void interruptIdleWorkers() 方法中断所有空闲线程,这一步是在逐步清除线程池中的线程,先把空闲的线程先清了。
- 调用 void tryTerminate() 方法是尝试去彻底关闭线程池,为什么是尝试呢?是因为还有任务没有执行完成,那么就不能关闭线程池。所以只要线程池中还有线程,那么就等这些线程执行完任务空闲下来,然后中断它们,直至线程池中的线程数为 0。当线程池中没有线程了,线程池也就彻底关闭了。
如果大家有任何疑问,请在下方留言或评论。
上一章Java线程池核心(七):线程池中的各种线程数
下一章Java线程池核心(九):从源码角度看shutdownNow方法做了什么
学习小组加入同步学习小组,共同交流与进步。
欢迎加入“人人都是程序员”编程圈子,与圈友一起交流讨论。
版权声明原创不易,未经允许不得转载!
,