赞
踩
前面针对graceful shutdown写了两篇文章
第一篇:
https://blog.csdn.net/chenshm/article/details/139640775
只考虑了阻塞线程,没有考虑异步线程
第二篇:
https://blog.csdn.net/chenshm/article/details/139702105
第二篇考虑了多线程的安全性,包括异步线程。
因为第二篇的写法还不够优美,它存在以下缺陷。
确认graceful shutdown settings
添加第一个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 @Async public 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); } } }
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 @Async public 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方法,验证全局生效。
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 { @Resource Demo1Service demo1Service; @Resource Demo2Service 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(); } }
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 { @Bean public 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; } }
Administrator@USER-20230930SH MINGW64 /d/git/micro-service-logs-tracing
$ curl http://localhost:8080/api/sandwich
日志完美验证了我们的期待。 我设置的“sandwich-async-pool-”线程名前缀也在两个线程日志中体现了。进一步证明AsyncConfig对所有@Async注解修饰的异步线程全局有效。
这是为什么呢?
当我在Spring配置中通过@Bean定义了一个ThreadPoolTaskExecutor实例,并且在同一配置类或其他被扫描到的配置类中启用了@EnableAsync注解时,这个自定义线程池会自动与Spring的异步任务执行机制关联起来。这一过程背后的原理涉及到Spring的异步任务执行器(AsyncConfigurer接口)的自动配置和代理机制,具体原因如下:
- Spring的自动装配(Auto Configuration): Spring Boot利用自动配置(auto-configuration)机制来简化配置。当它检测到@EnableAsync注解时,会自动寻找并配置一个TaskExecutor(线程池)来执行@Async标记的方法。如果在应用上下文中存在多个TaskExecutor的Bean,Spring通常会选择一个合适的Bean作为默认的异步执行器。自定义的ThreadPoolTaskExecutor Bean由于是明确配置的,因此优先级较高,自然成为首选。
- AsyncConfigurer接口: 当我使用@EnableAsync时,实际上是在告诉Spring去查找实现了AsyncConfigurer接口的配置类。如果我没有直接实现这个接口并提供自定义配置,Spring会使用默认的配置。但是,如果我提供了自定义的ThreadPoolTaskExecutor Bean,Spring会认为这是我希望用于异步任务的线程池。
- Spring AOP代理: @Async注解的方法在运行时会被Spring的AOP(面向切面编程)机制代理。这个代理逻辑会检查是否有配置好的TaskExecutor,如果有(比如我自定义的ThreadPoolTaskExecutor),就会使用这个线程池来执行方法,从而实现了异步调用。
- Bean的命名和类型匹配: 默认情况下,Spring在查找执行器时会优先考虑那些名为taskExecutor的Bean,这也是为什么在配置ThreadPoolTaskExecutor时通常会使用这个名字。当然,即使不叫这个名字,也可以通过实现AsyncConfigurer接口并重写getAsyncExecutor方法来指定使用的线程池。
综上所述,自定义的ThreadPoolTaskExecutor之所以能成为Spring异步任务执行的默认线程池,是因为Spring的自动配置逻辑、AOP代理机制以及通过配置明确指定了这个线程池的使用。
至此,graceful shutdown已经可以使多线程,高并发的项目在做release的时候,线程安全性得到保障。 特别是一些长处理的schedul job项目(其中好多job为了提交效率,用了异步机制),经过这样优化之后,release的信心是不是增强了好多。
写文章不容易,如果对您有用,请点个关注支持一下博主再走。谢谢。
如果有更好见解的朋友,请在评论区给出您的指导意见,感谢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。