iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >在Spring环境中正确关闭线程池的姿势
  • 542
分享到

在Spring环境中正确关闭线程池的姿势

Spring环境关闭线程池Spring关闭线程池 2023-05-14 09:05:09 542人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录前言线程池正确关闭的姿势应用中如何正确关闭线程池解决方案总结思考前言 在Java System#exit 无法退出程序的问题一文末尾提到优雅停机的一种实现方案,要借助Shutdo

前言

在Java System#exit 无法退出程序的问题一文末尾提到优雅停机的一种实现方案,要借助Shutdown Hook进行实现,本文,将继续探索优雅停机中遇到的一些问题:应用中线程池的优雅关闭

线程池正确关闭的姿势

在这一节,先不讨论应用中线程池该如何优雅关闭以达到优雅停机的效果,只是简单介绍一下线程池正确关闭的姿势

为简化讨论的复杂性,本文的线程池均是指jdk中的java.util.concurrent.ThreadPoolExecutor

正确关闭线程池的关键是 shutdown + awaitTermination或者 shutdownNow + awaitTermination

一种可能的使用姿势如下:

ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> {
    // do task
});

// 执行shutdown,将会拒绝新任务提交到线程池;待执行的任务不会取消,正在执行的任务也不会取消,将会继续执行直到结束
executorService.shutdown();

// 执行shutdownNow,将会拒绝新任务提交到线程池;取消待执行的任务,尝试取消执行中的任务
// executorService.shutdownNow();

// 超时等待线程池完毕
executorService.awaitTermination(3, TimeUnit.SECONDS);

一个任务会有如下几个状态:

  • 未提交,此时可以将任务提交到线程池
  • 已提交未执行,此时任务已在线程池的队列中,等待着执行
  • 执行中,此时任务正在执行
  • 执行完毕

那么,执行shutdown方法或shutdownNow方法之后,将会影响任务的状态

shutdown

  • 拒绝新任务提交
  • 待执行的任务不会取消
  • 正在执行的任务也不会取消,将继续执行

shutdownNow

  • 拒绝新任务提交
  • 取消待执行的任务
  • 尝试取消执行中的任务(仅仅是做尝试,成功与否取决于是否响应InterruptedException,以及对其做出的反应)

接下来看一下java doc对这两个方法的描述:

shutdown: Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.
This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

shutdownNow: Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.
This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.
There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt, so any task that fails to respond to interrupts may never terminate.

Java doc 提到,这两个方法都不会等执任务执行完毕,如果需要等待,请使用awaitTermination。该方法带有超时参数:如果超时后任务仍然未执行完毕,也不再等待。毕竟应用总归要停机重启,而不可能无限等待下去,因此超时机制是提供给用户的最后一道底线

综上,shutdown(Now) + awaitTermination 确实是实现线程池优雅关闭的关键

应用中如何正确关闭线程池

这一节内容其实才是本文要介绍的重心。上一小节内容我们知道了如何优雅关闭线程池,但那是一般意义上方法论指导,如果将线程池运用于我们的应用中,譬如Spring Boot环境中,复杂度将会变得不一样

本一节,将会介绍线程池在spring (Boot)环境中优雅关闭遇到的一个问题跟挑战,以及解决方案

注:本节使用Spring Boot举例,仅仅是因为它的应用面广,受众多,大家容易理解,并不代表只在该环境下才会出问题。在纯Spring、甚至非Spring环境,都有可能出现问题

场景1

我们来假设一个场景,有了场景的铺垫,对问题的理解会简单一些

@Resource
private RedisTemplate<String, Integer> redisTemplate;

// 自定义线程池
public static ExecutorService executorService = Executors.newFixedThreadPool(1);

@GetMapping("/incr")
public void incr() {
    executorService.execute(() -> {
        // 依赖Redis进行计数
        redisTemplate.opsForValue().increment("demo", 1L);
    });
}
  • 自定义线程池,用于异步任务的执行。此处为演示方便使用Executors.newFixedThreadPool(1)生成了只有一个线程的线程池
  • 高并发请求/incr接口,每次请求该接口,都会往线程池中添加一个任务,任务异步执行的过程中依赖Redis

此时,要求停机发布新版本,按照Java System#exit 无法退出程序的问题文章,我们知道了优雅停机的一般步骤:

  • 切断上游流量入口,确保不再有流量进入到当前节点
  • 向应用发送kill 命令,在设定的时间内待应用正常关闭,若超时后应用仍然存活,则使用kill -9命令强制关闭
  • JVM接收到kill命令,会唤起应用中所有的Shutdown Hooks,等待Shutdown Hooks执行完毕便可以正常关机;与此同时,应用会接着处理在途请求,以确保不会向客户端抛出连接中断异常,实现无感知发布

一切看起来很美好,然而…

当JVM收到kill指令后,便会唤醒所有的Shutdown Hook,而其中有一个Shutdown Hook是Spring应用在启动之初注册的,它的作用是对Spring管理的Bean进行回收,并销毁ioc容器

那么问题就产生了:以我们的场景为例,线程池里的任务与Spring Shutdhwon Hook正在并发地执行着,一旦任务执行期依赖的资源先行被释放,那任务执行时必然会报错

在我们的场景中,就很有可能因为Redis连接被回收,从而导致redisTemplate.opsForValue().increment("demo", 1L);抛出异常,执行失败

如图示:

Jedis连接池先行被回收

下一刻,线程池里的任务尝试获取Jedis连接,失败并抛出异常

场景2

除了上述场景外,还有一个场景或许大家也经常会碰到:本地启动一个定时任务,按一定频率将数据从DB加载到Cache中

例如:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

scheduledExecutorService.scheduleWithFixedDelay(() -> {
    // load from db and put into cache
    // ...
    
}, 100, 100, TimeUnit.MILLISECONDS);
  • 每100ms向线程池里扔一个任务
  • 任务是:从DB中取出数据,放入缓存(例如Local Cache,Redis)

在Spring Shutdown Hook执行期间,新的任务仍然会产生,又或者旧的任务未执行完毕,一旦尝试获取DB资源,就可能由于资源被回收而获取失败,抛出异常

此时的系统关闭已经不优雅—任务执行有异常,这种异常可能对业务有损,我们应尽量避免类似问题的产生,而不是抱着"算了吧,反正产生这个问题的概率很低",或者"算了吧,反正异常对我目前业务影响也不大"的态度,这是技术人的基本修养,也是对自我提高的要求—目前业务影响不大,允许不优先解决,但是期望掌握一种解决方案,将来有一天如果碰到了对业务损伤比较大的场景,可以很有底气地说:我能行

解决方案

这个问题产生的根因,是Spring Shutdown Hook与线程池里的任务并发执行,有可能使任务依赖的资源被提前回收导致的。那么一个很直白的思路即是:在切断流量之后,能否让线程池先关闭,再执行Spring 的Shutdown Hook,避免依赖资源被提前回收?

顺着这个思路,有三个问题需要解决:

  • 线程池如何关闭
  • 线程池如何感知Spring Shutdown Hook将要被执行
  • 如何让线程池先于Spring Shutdown Hook关闭

对于第一个问题,本文的上一个小节线程池正确关闭的姿势已经给出了解决方案:即shutdown(Now) + awaitTermination

对于第二个问题,Spring Shutdown Hook被触发的时候,会主动发出一些事件,我们只要监听这些的事件,就能够做出相应的反应

对于第三个问题,我们只要在这些事件的监听器中先行将线程池关闭,再让程序走接下来的关闭流程即可

二、三涉及到Spring 的Shutdown Hook 执行过程,具体原理本篇按下不表,留待下一篇进行分析

从上图中可以看出,只要在destroyBeans之前关闭线程池即可,因此,有两种解决方案:

  • 监听Spring的ContextClosedEvent事件,在事件被触发时关闭线程池
  • 实现Lifecycle接口,并在其stop方法中关闭线程池

此处以监听ContextClosedEvent为例:

@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
    	  // 获取线程池
    	  // ...
    	  
    	  // 关闭线程池,并等待一段时间
        myExecutorService.shutdown();
        myExecutorService.awaitTermination(3, TimeUnit.SECONDS);
    }
}

此处大家或许能看出一些小问题:需要自行管理线程池。在Spring环境中,我们其实有更多的选择:使用Spring提供的org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor,并将实例交给Spring管理

代码如下:

// 将ThreadPoolTaskExecutor实例交给Spring管理
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(1);
    executor.setMaxPoolSize(1);
    
    // 告诉线程池,在销毁之前执行shutdown方法
    executor.setWaitForTasksToCompleteOnShutdown(true);
    // shutdown\shutdownNow 之后等待3秒
    executor.setAwaitTerminationSeconds(3);
    
    return executor;
}
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
    // 直接注入
    @Resource
    private ThreadPoolTaskExecutor executor;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
    		// 关闭线程池
        executor.destroy();
    }
}

注: ThreadPoolTaskExecutor的waitForTasksToCompleteOnShutdown + awaitTerminationSeconds等于ThreadPoolExecutor的shutdown + awaitTermination,且在定义线程池时就将优雅关闭行为一同定义完毕,实现了高内聚的目的

在Spring中使用ThreadPoolTaskExecutor,更便捷:

  • 不用再自行管理线程池,获取的时候也很方便,直接注入即可
  • 在需要关闭的时候,直接调用destroy方法即可实现优雅关闭

这样,Spring就会等到线程池关闭(超时)后,才会接着往下执行Bean的销毁、资源回收、应用上下文关闭的逻辑,确保被依赖资源不会被提前回收掉

总结

本篇以两种实际场景为例,抛出了一个很切合实际项目的问题:在Spring应用中如何正确地关闭线程池。文中指出,如果非正常关闭将可能会产生异常的问题,同时也分析了问题产生的原因并给出了相应的解决方案。下一篇,将会具体分析Spring Shutdown Hook执行过程,与诸君共同探索其中的奥秘

思考

本文虽以"Spring环境中正确关闭线程池"为背景进行讨论,然而实际上思维还可以更发散一些,可以不限于Spring环境,也不限于"关闭线程池"这个行为。更一般化地,在一个应用上下文环境中,许多的Bean有相互依赖的关系,这种依赖关系在应用启动及应用关闭之时需要格外地注意:在启动时,被依赖的Bean需要先行构造完毕;在关闭时,被依赖的Bean需要靠后销毁。依托这个思想,只要找到应用上下文提供给我们的扩展点,就可以达到目的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: 在Spring环境中正确关闭线程池的姿势

本文链接: https://www.lsjlt.com/news/203045.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 在Spring环境中正确关闭线程池的姿势
    目录前言线程池正确关闭的姿势应用中如何正确关闭线程池解决方案总结思考前言 在Java System#exit 无法退出程序的问题一文末尾提到优雅停机的一种实现方案,要借助Shutdo...
    99+
    2023-05-14
    Spring环境 关闭线程池 Spring关闭线程池
  • 在Spring环境中怎么正确关闭线程池
    这篇文章主要介绍“在Spring环境中怎么正确关闭线程池”,在日常操作中,相信很多人在在Spring环境中怎么正确关闭线程池问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”在Spring环境中怎么正确关闭线程池...
    99+
    2023-07-05
  • 浅谈java socket的正确关闭姿势
    java socket对应的是网络协议中的tcp,tcp的三次握手、四次挥手、11中状态什么的这里就不说了,不知道大家平常使用socket的时候如果不注意的情况下,会不会遇到各种异常...
    99+
    2024-04-02
  • 一文了解Java 线程池的正确使用姿势
    目录概述线程池介绍线程池创建ThreadPoolExecutor创建Executors创建newFixedThreadPoolnewCachedThreadPoolnewSingle...
    99+
    2022-11-13
    Java 线程池使用 Java 线程池
  • Webpack中使用环境变量的各种正确姿势
    目录写在前边业务代码使用环境变量使用webpack.DefinePlugin插件在业务代码中注入环境变量webpack.DefinePlugin引发的思考definePlugin所谓...
    99+
    2024-04-02
  • java中断线程的正确姿势完整示例
    目录Java停止线程的逻辑(协同、通知)Sleep是否会收到线程中断信号解决方法总结Java停止线程的逻辑(协同、通知) 在Java程序中,我们想要停止一个线程可以通过interru...
    99+
    2023-05-19
    java中断线程 java 线程
  • Windows环境下PHP开发,解决PATH问题的正确姿势
    在Windows环境下进行PHP开发时,经常会遇到PATH问题。这个问题很常见,但是解决起来并不难。本文将会介绍Windows环境下PHP开发的正确姿势,包括如何解决PATH问题。 环境变量 在Windows环境下,环境变量是非常重要的...
    99+
    2023-07-22
    开发技术 windows path
  • JS循环中正确使用async、await的姿势分享
    目录概览(循环方式 - 常用)声明遍历的数组和异步方法for 循环中使用map 中使用forEach 中使用filter 中使用附使用小结总结概览(循环方式 - 常用) f...
    99+
    2024-04-02
  • 怎么在java中关闭线程池
    本篇文章为大家展示了怎么在java中关闭线程池,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4...
    99+
    2023-06-14
  • 在springboot中添加mvc功能的正确姿势讲解
    springboot 添加mvc功能 先放出来几个类(包含注解或接口)来观摩一下 WebMvcConfigurer @EnableWebMvc WebMvc...
    99+
    2024-04-02
  • Java多线程导致CPU占用100%解决及线程池正确关闭方式
    简介 情景:1000万表数据导入内存数据库,按分页大小10000查询,多线程,15条线程跑。 使用了ExecutorService executor = Executors.newF...
    99+
    2024-04-02
  • 怎么在Golang中正确地关闭程序
    本篇内容介绍了“怎么在Golang中正确地关闭程序”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、什么是关闭关闭是指程序完成任务后,及时终...
    99+
    2023-07-05
  • 在日志中记录Java异常信息的正确姿势分享
    目录日志中记录Java异常信息遇到的问题原因分析正确的做法java异常在控制台和日志里面的打印记录1、e.printStackTrace()打印在哪里2、e.printStackTr...
    99+
    2024-04-02
  • C++ 技术中的异常处理:如何在多线程环境中正确处理异常?
    在多线程 c++++ 中,异常处理遵循以下原则:及时性、线程安全和明确性。实战中,可以通过使用 mutex 或原子变量来确保异常处理代码线程安全。此外,还要考虑异常处理代码的重入性、性能...
    99+
    2024-05-09
    多线程 异常处理 c++
  • 在 Uber FX 中实现后台进程正常关闭的正确方法是什么?
    在Uber FX中,实现后台进程正常关闭的正确方法是什么?这是许多人在使用Uber FX时经常遇到的问题。作为一款强大的后台任务处理框架,Uber FX提供了一种简单而有效的方法来管理...
    99+
    2024-02-09
  • 如何在Linux系统中正确设置Java的环境变量?
    在Linux系统中,Java是一种广泛使用的编程语言。在使用Java编译器和运行Java程序时,必须正确设置Java的环境变量。如果没有正确设置环境变量,那么可能会导致Java程序无法编译或运行。本文将介绍如何在Linux系统中正确设置Ja...
    99+
    2023-08-31
    分布式 linux path
  • java中线程池最实用的创建与关闭指南
    目录前言线程池创建 只需要执行shutdown就可以优雅关闭 执行shutdownNow关闭的测试 总结前言 在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我...
    99+
    2024-04-02
  • 如何在Windows环境下正确使用PHP中的路径函数?
    PHP是一种非常流行的脚本语言,可以用于开发Web应用程序。在PHP开发中,路径函数是非常重要的一部分,因为它们允许我们正确地引用文件和目录。 在Windows环境下,使用路径函数会有一些不同的注意事项。本文将向您介绍如何在Windows环...
    99+
    2023-08-10
    windows path 函数
  • 怎么关闭电脑正在运行中的程序
    这篇文章主要为大家展示了“怎么关闭电脑正在运行中的程序”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“怎么关闭电脑正在运行中的程序”这篇文章吧。具体步骤:同时点击Ctrl+Alt+Delete,打...
    99+
    2023-06-27
  • springboot 正确的在异步线程中使用request的示例代码
    目录起因:发现有人踩过坑,但是没解决尝试寻找官方支持尝试自己解决还是甩给官方解决结论起因: 有后端同事反馈在异步线程中获取了request中的参数,然后下一个请求是get请求的话,发...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作