当前位置: 首页 > news >正文

Spring @Async注解【总结记录】

1、简介:

从Spring3开始提供了@Async注解,用于异步方法调用,调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。

该注解可以被标注在类或方法(通常是Service层的方法)上,用于实现方法的异步执行,当被标注在类上时表明类中的所有方法都被指定的异步执行器执行。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {/*** A qualifier value for the specified asynchronous operation(s).* <p>May be used to determine the target executor to be used when executing* the asynchronous operation(s), matching the qualifier value (or the bean* name) of a specific {@link java.util.concurrent.Executor Executor} or* {@link org.springframework.core.task.TaskExecutor TaskExecutor}* bean definition.* <p>When specified on a class-level {@code @Async} annotation, indicates that the* given executor should be used for all methods within the class. Method-level use* of {@code Async#value} always overrides any value set at the class level.* @since 3.1.2*/String value() default "";
}

@Async注解要结合@EnableAysnc注解使用,需要在启动类或者配置类上开启异步模式。

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;@EnableAsync
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);log.info("Project running .....");}
}
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.ThreadPoolExecutor;@Configuration
@EnableAsync
public class ThreadExecutorConfiguration {@Bean("threadExecutor")public ThreadPoolTaskExecutor threadExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数:线程池创建时候初始化的线程数executor.setCorePoolSize(10);// 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程executor.setMaxPoolSize(20);// 缓冲队列:用来缓冲执行任务的队列executor.setQueueCapacity(10);// 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁executor.setKeepAliveSeconds(60);// 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池executor.setThreadNamePrefix("scheduler-thread-");// ThreadLocal信息及相關信息传递executor.setTaskDecorator(AsyncExecutorContext.getInstance());// 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());executor.initialize();return executor;}
}
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Service
public class AsyncService {@Asyncpublic void asyncMethod() {// 这个方法将在一个独立的线程中执行// 执行耗时的操作,如数据库查询、网络请求等}
}

2、@Async注解的返回值:

@Async注解的返回值只能是void或Future,主要有以下几种情况:

  1. 如果@Async方法返回void,则调用者不会得到任何返回值。
  2. 如果@Async方法返回Future,调用者可以通过java.util.concurrent.Future的get方法获取异步执行的结果。
  3. 如果@Async方法抛出异常,调用者需要处理这些异常。

下面例子中,asyncMethodWithVoidReturnType方法不会返回任何值:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Service
public class AsyncService {@Asyncpublic void asyncMethodWithVoidReturnType() {System.out.println("Executing async method with void return type");}
}

下面例子中,asyncMethodWithReturnType方法会返回一个Future<String>,表示异步执行的结果:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;@Service
public class AsyncService {@Asyncpublic Future<String> asyncMethodWithReturnType() {System.out.println("Executing async method with return type");try {// 模拟长时间运行的任务Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return new AsyncResult<>("Task Completed");}
}

在调用异步方法的类中,你可以通过调用Future的get()方法来获取结果,如下所示:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;@Component
@EnableAsync
public class SomeComponent {@Autowiredprivate AsyncService asyncService;public void doSomething() {Future<String> future = asyncService.asyncMethodWithReturnType();// 异步方法执行完成后获取结果String result = future.get();System.out.println(result);}
}

@Async方法的异常处理:

默认情况下,‌如果在@Async方法执行过程中抛出了异常,‌这个异常不会被传递到调用者线程,‌因此调用者无法直接捕获到这个异常。‌异常会被封装在一个ExecutionException中,‌如果方法返回的是Future或CompletableFuture,‌则可以通过这些API来访问异常。‌

为了正确处理这些异常,你可以采取以下几种策略:

  1. 通过在异步方法中捕获处理;
  2. 在调用异步方法的地方捕获异常;
  3. 配置自定义的异常处理器进行异常捕获与发布。

【第一种策略适合】被@Async修饰的方法没有返回值的情况,可以在异步方法中捕获异常进行个性化处理(进行打印异常日志、监控报警或包装异常并重新抛出一个自定义异常)。

【第二种策略适合】如果@Async方法返回一个Future或CompletableFuture,‌调用者可以在结果上调用get()方法来访问结果或异常。‌如果方法执行过程中抛出了异常,‌调用get()方法将抛出ExecutionException,‌可以通过ExecutionException.getCause()来获取实际的异常。‌

如果@Async方法返回一个CompletableFuture,‌可以使用exceptionally、‌handle等API来处理异常。‌

策略三,配置自定义的异常处理器进行异常捕获与发布:

在Spring框架中,‌全局异常处理器通常用于处理由Spring MVC控制器抛出的异常。‌然而,‌@Async方法是在不同的线程中异步执行的,‌因此它们抛出的异常不会直接传播到调用者的线程,‌也不会被Spring MVC的全局异常处理器直接捕获。‌

要实现全局异常处理器捕获@Async方法抛出的异常,‌你需要采用一些间接的方法。‌以下是一种可能的解决方案:‌

1.‌自定义异常类‌:‌
首先,‌定义一个自定义的异常类,‌用于封装@Async方法中抛出的异常信息。‌

2.‌异常捕获与发布‌:‌
在@Async方法内部,‌使用try-catch块捕获所有异常,‌并将异常信息封装到自定义异常类中。‌然后,‌你可以使用Spring的事件发布机制(‌ApplicationEventPublisher)‌来发布一个包含异常信息的事件。‌

3.‌事件监听与处理‌:‌
创建一个事件监听器来监听上一步中发布的事件。‌在监听器中,‌你可以获取到异常信息,‌并进行相应的处理,‌比如记录日志、‌发送通知等。‌

4.‌配置事件监听器‌:‌
确保你的事件监听器被Spring容器管理,‌并且能够接收到发布的事件。‌

以下是一个简单的示例:‌

// 自定义异常类
public class AsyncMethodException extends RuntimeException {public AsyncMethodException(String message, Throwable cause) {super(message, cause);}
}// @Async方法
@Service
public class AsyncService {@Autowiredprivate ApplicationEventPublisher eventPublisher;@Asyncpublic void asyncMethod() {try {// 方法逻辑} catch (Exception e) {// 发布包含异常信息的事件AsyncMethodExceptionEvent event = new AsyncMethodExceptionEvent(this, e);eventPublisher.publishEvent(event);}}
}// 事件类
public class AsyncMethodExceptionEvent extends ApplicationEvent {private final Exception exception;public AsyncMethodExceptionEvent(Object source, Exception exception) {super(source);this.exception = exception;}public Exception getException() {return exception;}
}// 事件监听器
@Component
public class AsyncMethodExceptionListener implements ApplicationListener<AsyncMethodExceptionEvent> {@Overridepublic void onApplicationEvent(AsyncMethodExceptionEvent event) {// 处理异常Exception exception = event.getException();// 记录日志、‌发送通知等}
}

在这个示例中,‌当@Async方法asyncMethod抛出异常时,‌它会捕获异常并发布一个AsyncMethodExceptionEvent事件。‌然后,‌AsyncMethodExceptionListener监听器会接收到这个事件,‌并可以处理其中的异常。‌这样,‌你就可以在全局范围内处理@Async方法抛出的异常了。‌

3、@Async注解如何实现异步?

@Async的异步任务是基于Spring的AOP动态代理实现的,Spring容器启动初始化bean时,判断类中是否使用了@Async注解,创建切入点和切入点处理器,根据切入点创建代理,在调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池,实现异步执行。

所以要想使@Async生效则必须要将异步方法所在的类交给Spring管理,否则@Async不会生效。

@Async注解怎么选择Cglib还是JDK动态代理?

Spring框架默认使用Cglib库来创建代理,用于方法的异步执行。但是,如果你的类已经继承了一个类或者实现了一个接口,那么就不能使用Cglib来创建代理,因为Cglib是不能作用于最终类的。在这种情况下,Spring会使用JDK动态代理来创建代理。

如果你的类满足以下条件,Spring将使用Cglib来创建代理:

  • 类不是最终类(即可以被继承)。
  • 类不实现任何接口。

如果你的类满足以下条件,Spring将使用JDK动态代理来创建代理:

  • 类是最终类(即不可以被继承)。
  • 类实现了一个或多个接口。

你不需要选择使用Cglib还是JDK动态代理,Spring会自动根据情况选择合适的代理方式。如果你需要强制使用Cglib或JDK代理,可以通过配置来实现。例如,你可以在配置文件中添加以下属性来强制使用Cglib代理:

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="config" class="org.springframework.beans.factory.config.CustomScopeConfigurer"><property name="scopes"><map><entry key="myScope"><bean class="org.springframework.context.annotation.AnnotationConfigApplicationContext"><property name="beanFactoryPostProcessors"><bean class="org.springframework.aop.config.internalBeanConfigurerAspectJAutoProxy"><property name="proxyTargetClass" value="true"/></bean></property></bean></entry></map></property></bean>
</beans>

在这个例子中,proxyTargetClass 被设置为 true,这将强制使用Cglib代理。

如果你需要在代码中强制使用Cglib或JDK代理,也可以通过配置AsyncConfigurer来实现:

@Configuration
public class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newSingleThreadExecutor()));executor.initialize();return executor;}
}

在这个例子中,通过ThreadPoolTaskExecutor来配置异步执行器,并通过Executors.newSingleThreadExecutor()创建一个单线程的线程池,这将使用JDK动态代理。如果你想要使用Cglib代理,你需要使用Executors.newSingleThreadExecutor()创建线程池,并通过setProxyTargetClass(true)来配置。

请注意,强制使用Cglib代理可能会影响性能,因为Cglib代理比JDK代理更消耗资源。通常情况下,Spring会根据你的类情况自动选择最合适的代理方式。

4、当你使用@Async注解时,Spring会按照以下顺序查找用于异步执行的线程池:

  1. @Async注解通过value指定了线程池的名称(beanName),Spring会查找与该名称匹配的Executor实例;
  2. Spring查找自定义配置异步执行器(Async Executor),通过实现AsyncConfigurer接口来提供一个自定义的执行器实现全局线程池配置;
  3. Spring将会使用其默认配置的线程池(名称为 taskExecutor)来执行被@Async注解修饰的异步方法。
  4. 如果上述都没有找到,Spring会使用默认的线程池(SimpleAsyncTaskExecutor)。

@Async应用自定义线程池:
自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承AsyncConfigurer)。

自定义@Async 注解的线程池有如下模式:

  1. 重新实现接口AsyncConfigurer(或继承AsyncConfigurerSupport);
  2. 配置由自定义的TaskExecutor替代内置的任务执行器。

方法 1 通过实现AsyncConfigurer接口配置全局自定义线程池:

AsyncConfigurer接口是Spring框架用于全局配置异步执行器(即线程池)的核心接口。当我们的Spring应用需要统一管理所有异步任务的执行环境时,可以选择实现此接口。

@Configuration  
@EnableAsync  
public class GlobalAsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5); // 核心线程数executor.setMaxPoolSize(10); // 最大线程数executor.setQueueCapacity(20); // 队列容量executor.setThreadNamePrefix("global-"); // 线程名称前缀executor.initialize();return executor;}
}

在此示例中,GlobalAsyncConfig类实现了AsyncConfigurer接口,并在getAsyncExecutor()方法中配置了一个全局的线程池。这意味着,对于应用中所有标记为@Async的方法,默认都会使用这个配置好的线程池执行异步任务。

@Service  
public class MyService {@Asyncpublic void executeGlobalTask() {// 此方法将使用GlobalAsyncConfig中配置的线程池执行}
}

方法 2 指定使用自定义的线程池Excutor实例Bean并与@Async注解关联:

在Spring容器中注册一个线程池Bean,这种方式允许你根据业务需求更加灵活地管理和分配不同的线程池资源。

@Configuration  
public class CustomThreadPoolConfig {@Bean(name = "customExecutor")public ThreadPoolTaskExecutor customExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(3);executor.setMaxPoolSize(5);executor.setQueueCapacity(10);executor.setThreadNamePrefix("custom-");executor.initialize();return executor;}
}

现在,我们可以明确地将特定的线程池Bean与某个异步方法关联起来:

@Service  
public class MyService {@Async("customExecutor")public void executeCustomTask() {// 此方法将使用CustomThreadPoolConfig中名为customExecutor的线程池执行}
}

通过在@Async注解中指定"customExecutor",系统将优先使用这个名字注册在Spring容器中的线程池,而不是全局配置的线程池。

方法3 如果既没有配置全局的自定义线程池,也没有通过@Asycn的value指定自定义线程池,Spring会去查找beanName为taskExecutor的线程池:

@Configuration  
public class ThreadExecutorConfig {@Bean(name = "threadExecutor")public ThreadPoolTaskExecutor threadExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(3);executor.setMaxPoolSize(5);executor.setQueueCapacity(10);executor.setThreadNamePrefix("thread-");executor.initialize();return executor;}
}

这样即使使用@Async注解没有执行value值,也可以使用上面的beanName为threadExecutor的线程池。

@Service  
public class MyService {@Async("customExecutor")public void executeCustomTask() {// 此方法将使用CustomThreadPoolConfig中名为customExecutor的线程池执行}
}

Spring应用默认的线程池:
指在@Async注解在使用时,不指定线程池的名称。@Async的默认线程池为SimpleAsyncTaskExecutor。该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0时开启限流机制,默认关闭限流机制即concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。

线程池的默认配置:org.springframework.boot.autoconfigure.task.TaskExecutionProperties类中的
在这里插入图片描述
如果需要修改默认的配置可以在 yaml 或者 properties 中添加,修改默认配置:
在这里插入图片描述

5、@Async注解的失效场景?

  • 未在启动类或配置类上使用@EnableAsync注解开启异步功能;
  • 因为@Async注解是基于AOP动态代理实现的,如果对应的类没有被spring管理则会失效;
    • spirng没有扫描对应的Service类;
    • 对应的Service类没有添加@Service等spring注解;
    • 直接new对象不是通过依赖注入等方式;
    • Service方法内部调用(直接通过对象内部方法调用而不是通过spring代理类调用);
  • 异步方法返回值错误(只能是void或Future,如果是其它会使注解失效);
  • 使用final、static修饰(这种编译就会报错,基本不会存在);

相关文章:

  • 点对点专线的带宽管理和控制功能解析
  • 【AI趋势9】开源普惠
  • c语言练习题1
  • APP 整改要求 “未清晰明示高德SDK处理IP地址、SSID、BSSID的目的、方式和范围。”
  • 【QT】——1_QT学习笔记
  • 学懂C++(三十九):网络编程——深入详解 TCP 和 UDP 的区别和应用场景
  • Moodle与ONLYOFFICE集成如何实现智能教学管理
  • python中dataframe的iloc和loc的使用区别
  • 秋叶SD整合安装包更新了!8月最新版4.9【附下载】
  • Qt 0821作业
  • 用友crm客户关系管理help.php存在任意文件读取漏洞解析
  • 面试题目:(6)翻转二叉树
  • 机器学习十-欠拟合和过拟合
  • JavaScript - 事件监听
  • 批量自动校正图片、PDF文档方向工具
  • 分享一款快速APP功能测试工具
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • ERLANG 网工修炼笔记 ---- UDP
  • KMP算法及优化
  • ReactNative开发常用的三方模块
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • vue-cli在webpack的配置文件探究
  • vue中实现单选
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 前端js -- this指向总结。
  • 前端技术周刊 2019-02-11 Serverless
  • 日剧·日综资源集合(建议收藏)
  • 协程
  • 一、python与pycharm的安装
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • raise 与 raise ... from 的区别
  • #1015 : KMP算法
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • $refs 、$nextTic、动态组件、name的使用
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (六)Flink 窗口计算
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (转)甲方乙方——赵民谈找工作
  • .Net Framework 4.x 程序到底运行在哪个 CLR 版本之上
  • .net 验证控件和javaScript的冲突问题
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .net反编译工具
  • .net流程开发平台的一些难点(1)
  • .NET中winform传递参数至Url并获得返回值或文件
  • .sh
  • @Autowired多个相同类型bean装配问题
  • @Bean有哪些属性
  • @RequestBody与@RequestParam:Spring MVC中的参数接收差异解析
  • [2018][note]用于超快偏振开关和动态光束分裂的all-optical有源THz超表——
  • [BZOJ4016][FJOI2014]最短路径树问题
  • [C++][基础]1_变量、常量和基本类型
  • [CareerCup] 13.1 Print Last K Lines 打印最后K行
  • [CareerCup] 14.5 Object Reflection 对象反射