springboot优雅shutdown时异步线程安全优化

前面针对graceful shutdown写了两篇文章
第一篇:
https://blog.csdn.net/chenshm/article/details/139640775
只考虑了阻塞线程,没有考虑异步线程
第二篇:
https://blog.csdn.net/chenshm/article/details/139702105
第二篇考虑了多线程的安全性,包括异步线程。

1. 为什么还需要优化呢?

因为第二篇的写法还不够优美,它存在以下缺陷。

  • 只在一个service bean 里面对ExecutorService做predestroy,只能对一个service类的异步线程提供安全保障,其他service类的异步业务需要重写predestroy的逻辑,造成代码冗余。
  • 异步方法的写法比较麻烦,其他程序员并不常用。现在用springboot的程序员喜欢用@Async注解,随时随地可以把方法变成异步执行。
    从架构师的角度考虑的话,写代码尽量满足多数情况可用,易用,最好还是全局有效的,让其他程序员专注于写业务代码。
    接下来让我们实现@Async注解的异步方法在app graceful shutdow时保持线程安全。

2. 代码优化

  • 确认graceful shutdown settings
    graceful shutdown settings for springboot

  • 添加第一个servcie 的异步方法

package com.it.sandwich.service.impl;import com.it.sandwich.service.Demo2Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;/*** @Author 公众号: IT三明治* @Date 2024/6/16* @Description:*/
@Slf4j
@Service
@Component
public class Demo2ServiceImpl implements Demo2Service {@Override@Asyncpublic void feedUserInfoToOtherService(String userId) throws InterruptedException {for (int i = 0; i < 40; i++) {log.info("Demo2Service update {} login info to other services, service num: {}", userId, i+1);Thread.sleep(1000);}}
}
  • 添加第二个Servcie 的异步方法
package com.it.sandwich.service.impl;import com.it.sandwich.service.Demo2Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;/*** @Author 公众号: IT三明治* @Date 2024/6/16* @Description:*/
@Slf4j
@Service
@Component
public class Demo1ServiceImpl implements Demo1Service {@Override@Asyncpublic void feedUserInfoToOtherService(String userId) throws InterruptedException {for (int i = 0; i < 35; i++) {log.info("Demo1Service update {} login info to other services, service num: {}", userId, i+1);Thread.sleep(1000);}}
}

添加两个@Async方法,验证全局生效。

  • api接口
package com.it.sandwich.controller;import com.it.sandwich.base.ResultVo;
import com.it.sandwich.service.Demo1Service;
import com.it.sandwich.service.Demo2Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** @Author 公众号: IT三明治* @Date 2024/6/16* @Description:*/
@Slf4j
@RestController
@RequestMapping("/api")
public class DemoController {@ResourceDemo1Service demo1Service;@ResourceDemo2Service demo2Service;@GetMapping("/{userId}")public ResultVo<Object> getUserInfo(@PathVariable String userId) throws InterruptedException {log.info("userId:{}", userId);demo1Service.feedUserInfoToOtherService(userId);demo2Service.feedUserInfoToOtherService(userId);for (int i = 0; i < 30; i++) {log.info("updating user info for {}, waiting times: {}", userId, i+1);Thread.sleep(1000);}return ResultVo.ok();}
}
  • @Async有效的全局线程池配置
package com.it.sandwich.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;/*** @Author 公众号: IT三明治* @Date 2024/6/16* @Description:*/
@Configuration
@EnableAsync
public class AsyncConfig {@Beanpublic Executor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2); // 设置核心线程数executor.setMaxPoolSize(5); // 设置最大线程数executor.setQueueCapacity(100); // 设置队列容量executor.setThreadNamePrefix("sandwich-async-pool-"); // 自定义线程名称前缀executor.setWaitForTasksToCompleteOnShutdown(true); // 设置线程池关闭时是否等待任务完成executor.setAwaitTerminationSeconds(60); // 设置等待时间,如果你需要所有异步线程的安全退出,请根据线程池内敢长线程处理时间配置这个时间return executor;}
}

3. 验证代码

  • 重启服务
  • call api
Administrator@USER-20230930SH MINGW64 /d/git/micro-service-logs-tracing
$ curl http://localhost:8080/api/sandwich
  • shutdown app(Ctrl+F2)
  • 验证日志
    查看日志前我们先分析一下代码,我们一个api请求里面一共有三个线程,一个阻塞线程,两个@Async注解修饰的异步线程。阻塞线程的循环计数日志从1到30,Demo1Service 异步线程的循环计数日志从1到35,Demo2Service异步线程的循环计数日志从1到40。我们期待的结果是提前shutdown之后三个线程的计数日志都完整打印出来。
    graceful shutdown logs for three threads

日志完美验证了我们的期待。 我设置的“sandwich-async-pool-”线程名前缀也在两个线程日志中体现了。进一步证明AsyncConfig对所有@Async注解修饰的异步线程全局有效。
这是为什么呢?

4. AsyncConfig配置代码分析

当我在Spring配置中通过@Bean定义了一个ThreadPoolTaskExecutor实例,并且在同一配置类或其他被扫描到的配置类中启用了@EnableAsync注解时,这个自定义线程池会自动与Spring的异步任务执行机制关联起来。这一过程背后的原理涉及到Spring的异步任务执行器(AsyncConfigurer接口)的自动配置和代理机制,具体原因如下:

  1. Spring的自动装配(Auto Configuration): Spring Boot利用自动配置(auto-configuration)机制来简化配置。当它检测到@EnableAsync注解时,会自动寻找并配置一个TaskExecutor(线程池)来执行@Async标记的方法。如果在应用上下文中存在多个TaskExecutor的Bean,Spring通常会选择一个合适的Bean作为默认的异步执行器。自定义的ThreadPoolTaskExecutor Bean由于是明确配置的,因此优先级较高,自然成为首选。
  2. AsyncConfigurer接口: 当我使用@EnableAsync时,实际上是在告诉Spring去查找实现了AsyncConfigurer接口的配置类。如果我没有直接实现这个接口并提供自定义配置,Spring会使用默认的配置。但是,如果我提供了自定义的ThreadPoolTaskExecutor Bean,Spring会认为这是我希望用于异步任务的线程池。
  3. Spring AOP代理: @Async注解的方法在运行时会被Spring的AOP(面向切面编程)机制代理。这个代理逻辑会检查是否有配置好的TaskExecutor,如果有(比如我自定义的ThreadPoolTaskExecutor),就会使用这个线程池来执行方法,从而实现了异步调用。
  4. Bean的命名和类型匹配: 默认情况下,Spring在查找执行器时会优先考虑那些名为taskExecutor的Bean,这也是为什么在配置ThreadPoolTaskExecutor时通常会使用这个名字。当然,即使不叫这个名字,也可以通过实现AsyncConfigurer接口并重写getAsyncExecutor方法来指定使用的线程池。

综上所述,自定义的ThreadPoolTaskExecutor之所以能成为Spring异步任务执行的默认线程池,是因为Spring的自动配置逻辑、AOP代理机制以及通过配置明确指定了这个线程池的使用。
至此,graceful shutdown已经可以使多线程,高并发的项目在做release的时候,线程安全性得到保障。 特别是一些长处理的schedul job项目(其中好多job为了提交效率,用了异步机制),经过这样优化之后,release的信心是不是增强了好多。
写文章不容易,如果对您有用,请点个关注支持一下博主再走。谢谢。
如果有更好见解的朋友,请在评论区给出您的指导意见,感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/28317.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于C#开发web网页管理系统模板流程-参数传递

点击返回目录-> 基于C#开发web网页管理系统模板流程-总集篇-CSDN博客 前言 当用户长时间未在管理系统界面进行操作&#xff0c;或者用户密码进行了更改&#xff0c;显然用户必须重新登录以验证身份&#xff0c;如何实现这个功能呢&#xff1f; HTTP Cookie&#xff08;也叫 …

2024前端面试准备6-TS基础

1.TS基础类型有哪些&#xff1f;any void unknown never的区别&#xff1f; any 、Number、String、Boolean、Array 、元组、枚举、void、undefined、null、never any: 可以赋予任意类型的值&#xff0c;不进行类型检查&#xff0c;尽量不要用any void: 表示函数没有返回值 …

【Linux】 进程信号的发生

送给大家一句话&#xff1a; 何必向不值得的人证明什么&#xff0c;生活得更好&#xff0c;乃是为你自己。 -- 亦舒 进程信号的发生 1 何为信号2 信号概念的基础储备3 信号产生kill系统调用alarm系统调用异常core term Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢…

【教程】设置GPU与CPU的核绑(亲和力Affinity)

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 简单来说&#xff0c;核绑&#xff0c;或者叫亲和力&#xff0c;就是将某个GPU与指定CPU核心进行绑定&#xff0c;从而尽可能提高效率。 推荐与进程优先…

1055 集体照(测试点3, 4, 5)

solution 从后排开始输出&#xff0c;可以先把所有的学生进行排序&#xff08;身高降序&#xff0c;名字升序&#xff09;&#xff0c;再按照每排的人数找到中间位置依次左右各一个进行排列测试点3&#xff0c; 4&#xff0c; 5&#xff1a;k是小于10的正整数&#xff0c;则每…

SQL RIGHT JOIN 详解

SQL RIGHT JOIN 详解 引言 在SQL数据库查询中,JOIN操作用于结合两个或多个表中有关联的行。RIGHT JOIN是一种特殊的JOIN类型,它基于两个表之间的关联,从右表(即RIGHT JOIN后面的表)中返回所有行,即使在左表中没有匹配的行。如果左表中有匹配的行,则RIGHT JOIN还会从左…

线程池ThreadPoolExecutor源码分析

一、线程池基本概念和线程池前置知识 1.1 Java中创建线程的方式有哪些 传统答案&#xff1a; 继承Thread类 通过继承Thread类并重写其run方法来创建线程。具体步骤包括定义Thread类的子类&#xff0c;在子类中重写run方法以实现线程的具体逻辑&#xff0c;然后创建子类的实例…

Unity的三种Update方法

1、FixedUpdate 物理作用——处理物理引擎相关的计算和刚体的移动 (1) 调用时机&#xff1a;在固定的时间间隔内&#xff0c;而不是每一帧被调用 (2) 作用&#xff1a;用于处理物理引擎的计算&#xff0c;例如刚体的移动和碰撞检测 (3) 特点&#xff1a;能更准确地处理物理…

【C/C++】实参与形参的区别

在编程中&#xff0c;形参&#xff08;形式参数&#xff09;和实参&#xff08;实际参数&#xff09;是函数调用中的两个基本概念&#xff0c;它们在函数定义和函数调用中扮演着不同的角色。 形参&#xff08;Formal Parameters&#xff09;&#xff1a; 形参是在函数定义时声明…

植物大战僵尸杂交版全新版v2.1解决全屏问题

文章目录 &#x1f68b;一、植物大战僵尸杂交版❤️1. 游戏介绍&#x1f4a5;2. 如何下载《植物大战僵尸杂交版》 &#x1f680;二、解决最新2.1版的全屏问题&#x1f308;三、画质增强以及减少闪退 &#x1f68b;一、植物大战僵尸杂交版 《植物大战僵尸杂交版》是一款在原版《…

Es 索引查询排序分析

文章目录 概要一、Es数据存储1.1、_source1.2、stored fields 二、Doc values2.1、FieldCache2.2、DocValues 三、Fielddata四、Index sorting五、小结六、参考 概要 倒排索引 优势在于快速的查找到包含特定关键词的所有文档&#xff0c;但是排序&#xff0c;过滤、聚合等操作…

内存分配器性能优化

背景 在之前我们提到采用自定义的内存分配器来解决防止频繁 make 导致的 gc 问题。gc 问题本质上是 CPU 消耗&#xff0c;而内存分配器本身如果产生了大量的 CPU 消耗那就得不偿失。经过测试初代内存分配器实现过于简单&#xff0c;产生了很多 CPU 消耗&#xff0c;因此必须优…

[Day 11] 區塊鏈與人工智能的聯動應用:理論、技術與實踐

區塊鏈中的加密技術 介紹 區塊鏈技術被認為是現代信息技術的重大突破之一&#xff0c;其應用範圍從加密貨幣到供應鏈管理、醫療健康等各個領域。加密技術在區塊鏈中扮演著至關重要的角色&#xff0c;確保了數據的安全性、完整性和不可篡改性。本文將深入探討區塊鏈中的加密技…

重装系统,以及设置 深度 学习环境

因为联想y7000在ubantu系统上连不到wifi,所以打算弄双系统 第一步&#xff1a;下载win10镜像&#xff0c;之后在系统用gparted新建个分区&#xff0c;格式化成ntfs&#xff0c;用来装win10系统 第二步&#xff0c;制作win10启动盘&#xff0c;这个需要先把u盘用disks格式化&a…

跨语言翻译的突破:使用强化学习与人类反馈提升机器翻译质量

在人工智能领域&#xff0c;知识问答系统的性能优化一直是研究者们关注的焦点。现有的系统通常面临知识更新频繁、检索成本高、以及用户提问多样性等挑战。尽管采用了如RAG&#xff08;Retrieval-Augmented Generation&#xff09;和微调等技术&#xff0c;但它们各有利弊&…

C++ 45 之 赋值运算符的重载

#include <iostream> #include <string> #include <cstring> using namespace std;class Students05{ public:int m_age;char* m_name;Students05(){}Students05(const char* name,int age){// 申请堆空间保存m_name;this->m_name new char[strlen(name)…

案例 采用Springboot默认的缓存方案Simple在三层架构中完成一个手机验证码生成校验的程序

案例 Cacheable 是 Spring Framework 提供的一个注解&#xff0c;用于在方法执行前先检查缓存&#xff0c;如果缓存中已存在对应的值&#xff0c;则直接返回缓存中的值&#xff0c;而不执行该方法体。如果缓存中不存在对应的值&#xff0c;则执行方法体&#xff0c;并将方法的…

免费下载全球逐日气象站点数据

环境气象数据服务平台近期升级了NOAA GSOD 全球逐日气象站点数据&#xff08;NOAA Global Surface Summary of the Day &#xff09;的检索方式&#xff0c;升级后&#xff0c;用户无需注册&#xff0c;即可以在平台上下载全球逐日气象站点数据。 检索方式&#xff1a; 1. 访…

Redis数据结构之字符串(sds)

Redis数据结构之字符串(sds) redisObject 定义如下 struct redisObject {unsigned type:4; //数据类型unsigned encoding:4; /*encoding 编码格式&#xff0c;及存储数据使用的数据结构&#xff0c;同一类型的数据&#xff0c;Redis 会根据数据量&#xff0c;占用内…

Python学习打卡:day07

day7 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day753、列表的常用操作课后练习题54、列表的循环遍历列表的遍历—— while 循环列表的遍历—— for 循环while 循环和 for 循环的对比练习 55、元组…