写在开头


面试官:“小伙子,线程池使用过吗,来聊一聊它吧!”

我:“好的,然后巴拉巴拉一顿输出之前看过的build哥线程池十八问...”

面试官满意的点了点头,紧接着问道:“那你知道如何优雅的关闭线程池吗?”

我:“知道知道,直接调用shutdownNow()方法就好了呀!”

面试官脸色一变,微怒道:“粗鲁!你给我滚出去!!!”


优雅的关闭线程池

哈哈,上面的场景是build哥臆想出来的面试画面,我们现在步入正题,来看一看在线程池使用完成后如何优雅的关闭线程池。

在JDK 1.8 中,Java 并发工具包中 java.util.concurrent.ExecutorService 提供了 shutdown()、shutdownNow()这两种接口方法去关闭线程池,我们分别看一下。

shutdown()

public void shutdown() {
final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁
mainLock.lock(); // 加锁以确保独占访问 try {
checkShutdownAccess(); // 检查是否有关闭的权限
advanceRunState(SHUTDOWN); // 将执行器的状态更新为SHUTDOWN
interruptIdleWorkers(); // 中断所有闲置的工作线程
onShutdown(); // ScheduledThreadPoolExecutor中的挂钩方法,可供子类重写以进行额外操作
} finally {
mainLock.unlock(); // 无论try块如何退出都要释放锁
}
tryTerminate(); // 如果条件允许,尝试终止执行器
}

在shutdown的源码中,会启动一次顺序关闭,在这次关闭中,执行器不再接受新任务,但会继续处理队列中的已存在任务,当所有任务都完成后,线程池中的线程会逐渐退出。

我们写一个小的demo来使用shutdown():

public class TestService{
public static void main(String[] args) {
//创建固定 3 个线程的线程池,测试使用,工作中推荐ThreadPoolExecutor
ExecutorService threadPool = Executors.newFixedThreadPool(3); //向线程池提交 10 个任务
for (int i = 1; i <= 10; i++) {
final int index = i;
threadPool.submit(() -> {
System.out.println("正在执行任务 " + index);
//休眠 3 秒,模拟任务执行
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
} //休眠 4 秒
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
} //关闭线程池
threadPool.shutdown();
}
}

在这段测试代码中,我们构造了一个包含固定3线程数的线程池,循环提交10个任务,每个任务休眠3秒,但主程序休眠4秒后,会掉用shutdown方法,理论上,在第二个时间循环中,线程池被停止,所以最多执行完6个任务,但从输出中,我们丝毫感受不好线程何时被停止了。

输出:

正在执行任务 1
正在执行任务 3
正在执行任务 2
正在执行任务 4
正在执行任务 5
正在执行任务 6
正在执行任务 7
正在执行任务 8
正在执行任务 9
正在执行任务 10

shutdownNow()

/**
* 尝试停止所有正在执行的任务,停止处理等待的任务,
* 并返回等待处理的任务列表。
*
* @return 从未开始执行的任务列表
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks; // 用于存储未执行的任务的列表
final ReentrantLock mainLock = this.mainLock; // ThreadPoolExecutor的主锁
mainLock.lock(); // 加锁以确保独占访问
try {
checkShutdownAccess(); // 检查是否有关闭的权限
advanceRunState(STOP); // 将执行器的状态更新为STOP
interruptWorkers(); // 中断所有工作线程
tasks = drainQueue(); // 清空队列并将结果放入任务列表中
} finally {
mainLock.unlock(); // 无论try块如何退出都要释放锁
}
tryTerminate(); // 如果条件允许,尝试终止执行器 return tasks; // 返回队列中未被执行的任务列表
}

与shutdown不同的是shutdownNow会尝试终止所有的正在执行的任务,清空队列,停止失败会抛出异常,并且返回未被执行的任务列表。

由于shutdownNow会有返回值,所以我们将上面的测试案例稍作改动后输出结果为:

这种会在控制台抛出异常的方式,同样也不优雅,所以我们继续往下看!

shutdown()+awaitTermination(long timeout, TimeUnit unit)

awaitTermination(long timeout, TimeUnit unit)是可以允许我们在调用shutdown方法后,再设置一个等待时间,如设置为5秒,则表示shutdown后5秒内线程池彻底终止,返回true,否则返回false;

这种方式里,我们将shutdown()结合awaitTermination(long timeout, TimeUnit unit)方法去使用,注意在调用 awaitTermination() 方法时,应该设置合理的超时时间,以避免程序长时间阻塞而导致性能问题,而且由于这个方法在超时后也会抛出异常,因此,我们在使用的时候要捕获并处理异常!

public class TestService{
public static void main(String[] args) {
//创建固定 3 个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3); //向线程池提交 10 个任务
for (int i = 1; i <= 10; i++) {
final int index = i;
threadPool.submit(() -> {
System.out.println("正在执行任务 " + index);
//休眠 3 秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
//关闭线程池,设置等待超时时间 3 秒
System.out.println("设置线程池关闭,等待 3 秒...");
threadPool.shutdown();
try {
boolean isTermination = threadPool.awaitTermination(3, TimeUnit.SECONDS);
System.out.println(isTermination ? "线程池已停止" : "线程池未停止");
} catch (InterruptedException e) {
e.printStackTrace();
} //再等待超时时间 20 秒
System.out.println("再等待 20 秒...");
try {
boolean isTermination = threadPool.awaitTermination(20, TimeUnit.SECONDS);
System.out.println(isTermination ? "线程池已停止" : "线程池仍未停止,请检查!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出:

设置线程池关闭,等待 3 秒...
正在执行任务 1
正在执行任务 2
正在执行任务 3
正在执行任务 4
正在执行任务 5
线程池未停止
再等待 20 秒...
正在执行任务 6
正在执行任务 7
正在执行任务 8
正在执行任务 9
正在执行任务 10
线程池已停止

从输出中我们可以看到,通过将两种方法结合使用,我们监控了整个线程池关闭的全流程,实现了优雅的关闭!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!的更多相关文章

  1. 面试官不按套路,竟然问我Java线程池是怎么统计线程空闲时间?

    背景介绍: 你刚从学校毕业后,到新公司实习,试用期又被毕业,然后你又不得不出来面试,好在面试的时候碰到个美女面试官! 面试官: 小伙子,我看你简历上写的项目中用到了线程池,你知道线程池是怎样实现复用线 ...

  2. 使用RunTime.getRunTime().addShutdownHook优雅关闭线程池

    有时候我们用到的程序不一定总是在JVM里面驻守,可能调用完就不用了,释放资源. RunTime.getRunTime().addShutdownHook的作用就是在JVM销毁前执行的一个线程.当然这个 ...

  3. 字节跳动Java研发面试99题(含答案):JVM+Spring+MySQL+线程池+锁

    JVM的内存结构 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1. Java虚拟机栈:线程私有:每个方法在执行的时候会创建一个栈帧,存储了局部变量表, ...

  4. ThreadPoolExecutor 优雅关闭线程池的原理.md

    经典关闭线程池代码 ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.shutdo ...

  5. Java面试专题-多线程篇(2)- 锁和线程池

  6. 如何优雅的关闭Java线程池

    面试中经常会问到,创建一个线程池需要哪些参数啊,线程池的工作原理啊,却很少会问到线程池如何安全关闭的. 也正是因为大家不是很关注这块,即便是工作三四年的人,也会有因为线程池关闭不合理,导致应用无法正常 ...

  7. 论如何优雅的自定义ThreadPoolExecutor线程池

    更好的markDown阅读体验可直接访问我的CSDN博客:https://blog.csdn.net/u012881584/article/details/85221635 前言 线程池想必大家也都用 ...

  8. 聊聊面试中的 Java 线程池

    ​背景 关于 Java 的线程池我想大家肯定不会陌生,在工作中或者自己平时的学习中多多少少都会用到,那你真的有了解过底层的实现原理吗?还是说只停留在用的阶段呢?而且关于 Java 线程池也是在面试中的 ...

  9. 深入理解Java之线程池(爱奇艺面试)

    爱奇艺的面试官问 (1) 线程池是如何关闭的 (2) 如何确定线程池的数量 一.线程池销毁,停止线程池 ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown() ...

  10. ThreadPoolExecutor源码分析-面试问烂了的Java线程池执行流程,如果要问你具体的执行细节,你还会吗?

    Java版本:8u261. 对于Java中的线程池,面试问的最多的就是线程池中各个参数的含义,又或者是线程池执行的流程,彷佛这已成为了固定的模式与套路.但是假如我是面试官,现在我想问一些更细致的问题, ...

随机推荐

  1. 重新点亮linux 命令树————内存与文件系统的查看[二十七]

    前言 简单介绍一下内存的查看. 正文 常用命令. free top 查看磁盘使用率: fdisk df du du和ls的区别 free -h 查看内存使用率: free -g 显示按G来显示,-m用 ...

  2. 剑指offer11(Java)-旋转数组中的最小值(简单)

    题目: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. 给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转.请返回旋转数 ...

  3. 深度解析数据湖存储方案Lakehouse架构

    ​简介:从数据仓库.数据湖的优劣势,湖仓一体架构的应用和优势等多方面深度解析Lakehouse架构. 作者:张泊 Databricks 软件工程师 ​ Lakehouse由lake和house两个词组 ...

  4. 阿里云徐立:面向容器和 Serverless Computing 的存储创新

    ​简介:以上为大家分享了阿里云容器存储的技术创新,包括 DADI 镜像加速技术,为容器规模化启动奠定了很好的基础,ESSD 云盘提供极致性能,CNFS 容器网络文件系统提供极致的用户体验. 作者:徐立 ...

  5. Quick Audience组织和工作空间功能解读

    近期,Quick Audience完成了权限系统全面升级,可以解决集团企业不同品牌.不同运营组织,不同消费者运营的诉求,精细化保障企业数据访问安全,提升管控的灵活度.​ Quick Audience整 ...

  6. 在线工具的 UI 变迁

    V1. Ref:https://www.cnblogs.com/farwish/p/16823474.html

  7. [PHP] 浅谈 Laravel Authorization 的 gates 与 policies

    首先要区分 Authentication 与 Authorization,认证和授权,粗细有别. 授权(Authorization) 有两种主要方式,Gates 和 Policies. Gates 和 ...

  8. dotnet 读 WPF 源代码笔记 提升调试效率的 NamedObject 类型

    本文来聊聊 WPF 那些值得称赞的设计中的 NamedObject 类型.在 WPF 中,有很多值得我学习的设计开发思想,其中就包括本文将要介绍的 NamedObject 类型.此类型的定义仅仅只是为 ...

  9. dotnet 在国产 UOS 系统利用 dotnet tool 工具做文件传输

    我在一台设备上安装了 UOS 系统,但是我如何在我的主开发设备上和 UOS 系统传输文件?通过 dotnet tool 工具可以完成大部分的工作,当然,使用 dotnet tool 不仅做文件传输,还 ...

  10. "友链"

    欢迎来到我的友链小屋 展示本站所有友情站点,排列不分先后,均匀打乱算法随机渲染的喔!   友链信息 博客名称:麋鹿鲁哟博客网址:https://www.cnblogs.com/miluluyo/博客头 ...