写在开头


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

我:“好的,然后巴拉巴拉一顿输出之前看过的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. Triton部署mmdeploy导出的TensorRT模型失败篇

    记录一下历程,最终没有部署成功,应该是Ubantu系统版本的问题.现在没有时间搞了,先记录一下,后续用到再填坑. Triton demo git clone -b r22.06 https://git ...

  2. 基于locust全链路压测系统

    2021年中旬就计划着搭建一套压测系统,大约9月份已经搭建完成,使用至今还是比较稳定了,分享一下搭建思路及过程: 为什么选择Locust呢,因为Locust可以仅需要执行命令就可以完成压测任务,并且集 ...

  3. 一文剖析PolarDB HTAP的列存数据压缩

    简介: PolarDB MySQL是阿里云自研的云原生数据库,主要处理在线事务负载(OLTP, OnLine Transactional Processing),深受企业用户的青睐. 前言 数据库迁移 ...

  4. Koordinator 0.6:企业级容器调度系统解决方案,引入 CPU 精细编排、资源预留与全新的重调度框架

    简介: 经过社区多位成员的贡献,Koordinator 0.6 版本正式发布.相较于上一个版本 0.5,新版本进一步完善了 CPU 精细化编排能力,更好的兼容原生用法:支持了资源预留的能力(Reser ...

  5. SpringBoot Admin2.0 集成 Java 诊断神器 Arthas 实践

    简介: 项目最初使用 Arthas 主要有两个目的: 1. 通过 arthas 解决实现测试环境.性能测试环境以及生产环境性能问题分析工具的问题. 2. 通过使用 jad.mc.redefine 功能 ...

  6. LlamaIndex 起步教程(本地模型)

    提示:确保您已先按照自定义安装步骤操作. 这是一个著名的"五行代码"起步示例,使用本地 LLM(大语言模型)和嵌入模型.我们将使用 BAAI/bge-small-en-v1.5 作 ...

  7. [GPT] 监测输入框被 js 设置了值 ?input 输入框被设置了 value 值,但是没有触发 change 事件?

    1. input 输入框被设置了 value 值,但是没有触发 change 事件 ? 如果输入框的 value 值是通过 JavaScript 代码直接设置的,那么不会触发 change 事件,这是 ...

  8. 2018-2-13-win10-uwp-右击选择-GridViewItem-

    title author date CreateTime categories win10 uwp 右击选择 GridViewItem lindexi 2018-2-13 17:23:3 +0800 ...

  9. JavaScript之变量解构赋值的使用

    引言 解构赋值是ES6中引入的一种能快速方便的进行变量赋值的方法,其主要也就是分为解构和赋值两部分内容.解构者,也就是匹配结构,然后分解结构进行赋值. 数组的解构赋值 使用 const arr = [ ...

  10. PostMan测试图片上传接口的方法

    一.选择POST后添加接口地址 二.选择Body下的from-data 注:Headers不要加参数 三.填写key,再key后的下拉选择file,然后选择文件 注:key并不是图片名称,而是接口接收 ...