• Spring 异步@Async注解用法 Spring @Async注解用法总结 Spring @Async基本用法示例


            Spring 异步@Async注解用法 Spring @Async注解用法总结 Spring @Async基本用法示例

    一、概述

            在日常开发的工作中,经常会使用异步进行开发。Spring 提供一个简单的注解 @Async ,即可实现异步的开发,无需创建线程池,简单明了。 本文将整理 @Async 的常见用法,包括:基础入门,获取返回值,配置线程池,异常处理等。

            @Async 注解实现原理,请自行查看源码,从:org.springframework.aop.interceptor.AsyncExecutionInterceptor 开始...

    二、简单用法

            1、Spring Boot 环境中,启动类上 使用 @EnableAsync , 即可启用 异步

            2、在类上或方法上,使用 @Async 注解,标记该方法为异步,可以通过 打印线程池名称验证。

            

    1. @EnableAsync
    2. @SpringBootApplication
    3. public class SpringBootTouristApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(SpringBootTouristApplication.class, args);
    6. }
    7. @Lazy
    8. @Autowired
    9. private SpringBootTouristApplication application;
    10. @Bean
    11. ApplicationRunner run(){
    12. ApplicationRunner run = (args)->{
    13. application.sayHi();
    14. };
    15. return run;
    16. }
    17. @Async
    18. public void sayHi(){
    19. System.out.println(Thread.currentThread().getName()+"=== async 异步");
    20. }
    21. }

        

                2.1、上述代码输出结果

    task-1=== async 异步

            3、@Async 注解,用在 类上,标记这个类方法都是异步的

    1. @Service
    2. @Async
    3. public class MessageAsync {
    4. public void sendMsg(long millis){
    5. System.out.println(Thread.currentThread().getName()+" sendMsg start ===");
    6. try {
    7. Thread.sleep(millis);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. System.out.println(MessageAsync.class.getName() + " sendMsg 方法运行中 。。。");
    12. System.out.println(Thread.currentThread().getName()+" sendMsg end ===");
    13. }
    14. /**
    15. * @Description: 使用指定的线程池
    16. * @return void
    17. * @version v1.0
    18. * @author wu
    19. * @date 2022/9/13 22:39
    20. */
    21. @Async("taskExecutor222")
    22. public void sendMsg222(long millis){
    23. System.out.println(Thread.currentThread().getName()+" sendMsg222 start ===");
    24. try {
    25. Thread.sleep(millis);
    26. } catch (InterruptedException e) {
    27. e.printStackTrace();
    28. }
    29. System.out.println(MessageAsync.class.getName() + " sendMsg222 方法运行中 。。。");
    30. System.out.println(Thread.currentThread().getName()+" sendMsg222 end ===");
    31. }
    32. }

    三、获取返回值

            1、Spring 提供了统一的异步返回结果:AsyncResult ,需要注意的是:方法return的AsyncResult 对象,方法的返回需要用 Future 接收; 若使用 AsyncResult 作为返回值,会导致异常 :ClassCastException

    org.springframework.scheduling.annotation.AsyncResult

            2、获取返回值,触发 ClassCastException

    1. @Async
    2. public AsyncResult getResult(Long mill){
    3. System.out.println(Thread.currentThread().getName()+" 携带返回值 AsyncResult FileAsync start ===");
    4. try {
    5. Thread.sleep(mill);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. System.out.println(FileAsync.class.getName() + " AsyncResult 方法运行中 。。。");
    10. System.out.println(Thread.currentThread().getName()+" 携带返回值 AsyncResult FileAsync end ===");
    11. String res = "AsyncResult 返回值,延迟"+mill+" ms";
    12. return new AsyncResult(res);
    13. }

            3、正常获取返回值

    1. @Async
    2. public Future getFuture(Long mill){
    3. System.out.println(Thread.currentThread().getName()+" 携带返回值 Future FileAsync start ===");
    4. try {
    5. Thread.sleep(mill);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. System.out.println(FileAsync.class.getName() + " getFuture 方法运行中 。。。");
    10. System.out.println(Thread.currentThread().getName()+" 方携带返回值 Future FileAsync end ===");
    11. String res = "getFuture 返回值,延迟"+mill+" ms";
    12. return new AsyncResult(res);
    13. }

    四、配置线程池

            1、方法一:配置全局线程池

    1. @Configuration
    2. public class AsyncPoolConfig {
    3. /**
    4. * 核心线程数 (默认线程数)
    5. */
    6. @Value("${pool.core-size:4}")
    7. // @Value("${pool.core-size:1}")
    8. private int corePoolSize;
    9. /**
    10. * 最大线程数
    11. */
    12. @Value("${pool.max-size:8}")
    13. // @Value("${pool.max-size:2}")
    14. private int maxPoolSize;
    15. /**
    16. * 允许线程空闲时间 - 单位:秒
    17. */
    18. @Value("${pool.keep-alive:60}")
    19. private int keepAliveSeconds;
    20. /**
    21. * 缓冲队列数
    22. */
    23. @Value("${pool.queue-capacity:5}")
    24. private int queueCapacity;
    25. /**
    26. * 线程前缀名称
    27. */
    28. @Value("${thread-name-prefix: @Async-线程池pool}")
    29. private String threadNamePrefix;
    30. //设置@Async的默认线程池
    31. @Bean("taskExecutor")
    32. public ThreadPoolTaskExecutor taskExecutor() {
    33. ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    34. pool.setCorePoolSize(corePoolSize);//核心线程池数
    35. pool.setMaxPoolSize(maxPoolSize); // 最大线程数
    36. pool.setQueueCapacity(queueCapacity);//队列容量,当核心线程数达到最大时,新任务会放在队列中排队等待执行
    37. pool.setKeepAliveSeconds(keepAliveSeconds);//线程空闲时间
    38. pool.setAllowCoreThreadTimeOut(false);//核心线程会一直存活,即使没有任务需要执行。(默认false)时,核心线程会超时关闭
    39. pool.setThreadNamePrefix(threadNamePrefix);//线程前缀名称
    40. // 线程池的拒绝策略 --- 继续执行
    41. // pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    42. // 线程池的拒绝策略 --- 抛出异常 (默认方式)
    43. pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    44. // 初始化
    45. // pool.initialize();
    46. return pool;
    47. }
    48. }

            2、方法二:实现 AsyncConfigurer 接口 ,重写 getAsyncExecutor,配置指定线程池

    1. @Configuration
    2. public class AsyncConifg implements AsyncConfigurer {
    3. @Override
    4. public Executor getAsyncExecutor() {
    5. // 配置指定的线程池
    6. final ExecutorService executorService = Executors.newFixedThreadPool(10);
    7. return executorService;
    8. }
    9. }

            3、当项目中有多个线程池时,可以通过 @Async 注解的 value属性,使用指定的线程池

    1. // 使用 beanName为: taskExecutor222 线程池
    2. @Async("taskExecutor222")

          

      4、注意:方法一 和 方法二的 优先级问题, 没有进行测试。

    五、异常处理

            1、当异步执行的时候,遇到异常,该如何处理呢?

            2、假设如下方法,执行出现异常:

    1. @Async
    2. public String exp(int a, String b){
    3. System.out.println(Thread.currentThread().getName()+" ; exp start ===");
    4. Object str = null ;
    5. final boolean res = str.equals("");
    6. System.out.println(Thread.currentThread().getName()+" ; exp end === res = "+res);
    7. return "exp";
    8. }

            2.1、输出结果如下:

    1. task-2 ; exp start ===
    2. [ERROR] org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler:39 : Unexpected exception occurred invoking async method: public java.lang.String com.runcode.springboottourist.async.ExpAsync.exp(int,java.lang.String)
    3. java.lang.NullPointerException
    4. ...

            2.2、注意:" task-2 ; exp end === res = " ,语句没有输出 ..

            2.3、方法体内,增加 try-catch

    1. @Async
    2. public String expFix(){
    3. System.out.println(Thread.currentThread().getName()+" ; expFix start ===");
    4. boolean res = false;
    5. try {
    6. Object str = null ;
    7. res = str.equals("");
    8. } catch (Exception e) {
    9. e.printStackTrace();
    10. }
    11. /**
    12. * try-catch 后:end 语句会正常输出.
    13. */
    14. System.out.println(Thread.currentThread().getName()+" ; expFix end === res = "+res);
    15. return "expFix";
    16. }

            3、全局异常处理,实现 AsyncUncaughtExceptionHandler 接口,处理异常

    1. @Configuration
    2. public class AsyncExpConfig implements AsyncUncaughtExceptionHandler {
    3. @Override
    4. public void handleUncaughtException(Throwable ex, Method method, Object... params) {
    5. System.out.println("handleUncaughtException ===== start ======");
    6. System.out.println("异常的方法是:"+ method);
    7. System.out.println("exp detail info :"+ ExceptionUtils.getStackTrace(ex));
    8. System.out.println("参数 prams :"+ Arrays.toString(params));
    9. System.out.println("handleUncaughtException ===== end ======");
    10. }
    11. }

            4、配置异常处理接口: 实现 AsyncConfigurer 接口 ,重写 getAsyncUncaughtExceptionHandler 方法

    1. @Configuration
    2. public class AsyncConifg implements AsyncConfigurer {
    3. @Autowired
    4. private AsyncExpConfig asyncExpConfig;
    5. @Override
    6. public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    7. return asyncExpConfig;
    8. }
    9. }

            5.1、 再次执行,上述代码,异常信息变成了:

    1. task-2 ; exp start ===
    2. handleUncaughtException ===== start ======
    3. 异常的方法是:public java.lang.String com.runcode.springboottourist.async.ExpAsync.exp(int,java.lang.String)
    4. exp detail info :java.lang.NullPointerException
    5. at com.runcode.springboottourist.async.ExpAsyn ...
    6. 参数 prams :[1, 22]
    7. handleUncaughtException ===== end ======

    六、总结

            1、本文相对详细的记录@Async 注解的常见用法,可以满足日常大部分的开发需求。

            2、注意一点: 在同一个类中,是可以存在 异步方法和非异步方法的,要注意的是调用的方式 , 比如下面代码

    1. @Service
    2. public class AsyncTestService {
    3. /**
    4. * @Lazy : 解决循环依赖问题 circular reference
    5. */
    6. @Lazy
    7. @Autowired
    8. private AsyncTestService asyncTestService;
    9. public void sync(){
    10. System.out.println(Thread.currentThread().getName()+" ; sync method ..");
    11. /**
    12. * 该方法实际被 this.async(); 调用,this 没有被AOP代理增强,故 不会执行 @Async 异步方法
    13. */
    14. async();
    15. /**
    16. * 该方法被 AOP增强后的方法调用,会执行 @Async 异步方法
    17. */
    18. asyncTestService.async();
    19. }
    20. @Async
    21. public void async() {
    22. System.out.println(Thread.currentThread().getName()+" ; async 异步 method ..");
    23. }
    24. }

            2.1、输出结果如下:

    1. main ; sync method ..
    2. main ; async 异步 method ..
    3. @Async-线程池pool2 ; async 异步 method ..

            2.2、注意理解点: @Async是基于AOP实现的,普通的this调用,是没有被增强的,故而会导致方法调用无效; asyncTestService.async(); 方法调用,该类是AOP代理后增强 ... 可以通过 debug 观察 ...

     

    参考资料:

    Java 多线程 Runnable 与 Callable

    @Lazy 注解作用

    @Async 注解 实现原理没研究

  • 相关阅读:
    vim的配置及基础使用
    numpy公式
    【GOF】三种工厂模式~
    模拟实现十字路口交通灯管理系统(Java)
    EventBus详解 (详解 + 原理)
    基于北方苍鹰优化的BP神经网络(分类应用) - 附代码
    以太坊合并对NFT意味着什么?
    比较Visual Studio Code中的文件
    基于SSM的网络教学(作业)管理系统
    记一次G1垃圾回收线上调优的实践
  • 原文地址:https://blog.csdn.net/HaHa_Sir/article/details/128126282