关闭线程池方法一共有两个:void shutdown() 关闭线程池,继续执行完未完成的任务,不再接收新任务,我来为大家讲解一下关于java优雅关闭线程池,Java线程池核心八?跟着小编一起来看一看吧!

java优雅关闭线程池,Java线程池核心八

java优雅关闭线程池,Java线程池核心八

1.两个关闭线程池的方法

关闭线程池方法一共有两个:

  1. void SHUTDOWN()
  2. 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 个步骤:

  1. 获取线程池同步锁并加锁。
  2. 检查当前调用者有没有关闭线程的权限。
  3. 设置线程池状态为 SHUTDOWN
  4. 中断所有空闲线程。
  5. onShutdown() 方法中执行善后工作。
  6. 尝试关闭线程池。

比较重要的 3 步:

以上 3 步对应的方法:

interruptIdleWorkers() 方法在第五章第 2 小节分析过。

advanceRunState(int targetState) 方法是在如果想把线程池状态设置为 SHUTDOWNSTOP 时使用。

tryTerminate() 方法是在如果想把线程池状态设置为 TIDYINGTERMINATED 时使用。

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技术详解

一般需要将线程池状态设置为 SHUTDOWNSTOP 才使用 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(); } } }

一般需要将线程池状态设置为 TIDYINGTERMINATED 才使用 void tryTerminate() 方法。

该方法里面最重要的一个步骤就是只要线程池中还有线程,那么就等这些线程执行完任务空闲下来,然后中断它们,直至线程池中的线程数为 0

// 当当前线程数不为0时 if (workerCountOf(c) != 0) { // 中断一个空闲线程 interruptIdleWorkers(ONLY_ONE); // 结束方法 return; }

5.关键点

经过上面分析源码后,梳理使用 void shutdown() 方法关闭线程池关键点:

先调用 void advanceRunState(int targetState) 方法将线程池状态设置为 SHUTDOWNSTOP,这一步是为了不再接收新提交的任务,也不再添加新的线程。

再调用 void interruptIdleWorkers() 方法中断所有空闲线程,这一步是在逐步清除线程池中的线程,先把空闲的线程先清了。

最后调用 void tryTerminate() 方法是尝试去彻底关闭线程池,为什么是尝试呢?是因为还有任务没有执行完成,那么就不能关闭线程池。所以只要线程池中还有线程,那么就等这些线程执行完任务空闲下来,然后中断它们,直至线程池中的线程数为 0。当线程池中没有线程了,线程池也就彻底关闭了。

总结
  1. 获取线程池同步锁并加锁。
  2. 检查当前调用者有没有关闭线程的权限。
  3. 设置线程池状态为 SHUTDOWN
  4. 中断所有空闲线程。
  5. onShutdown() 方法中执行善后工作。
  6. 尝试关闭线程池。
答疑

如果大家有任何疑问,请在下方留言或评论。

上一章

Java线程池核心(七):线程池中的各种线程数

下一章

Java线程池核心(九):从源码角度看shutdownNow方法做了什么

学习小组

加入同步学习小组,共同交流与进步。

欢迎加入“人人都是程序员”编程圈子,与圈友一起交流讨论。

版权声明

原创不易,未经允许不得转载!

,