Java Spring的@Async的使用及注意事项

news/2025/1/14 6:14:50/

1、概念和用途

@Async是 Spring 框架提供的一个注解,用于标记一个方法,在一个单独的线程中异步执行。

这在处理一些耗时的操作(比如发送邮件、调用外部 API 等)时非常有用。

通过使用@Async,可以让这些操作在后台执行,而不会阻塞主线程,从而提高应用程序的性能和响应速度。

例如,在一个Web应用程序中,当用户提交一个订单后,可能需要发送一封确认邮件。如果使用同步方式,用户必须等待邮件发送完成后才能得到订单提交成功的响应。而使用@Async,可以让邮件发送操作在后台线程中进行,用户几乎可以立即得到订单提交成功的响应。

2、使用

2.1 启用异步支持

首先,需要在 Spring 配置类上添加@EnableAsync注解来开启异步方法执行功能。这个注解会扫描带有@Async标记的方法,并为它们创建独立的线程来执行。

示例配置类如下:

@Configuration
@EnableAsync
public class AppConfig {// 可以在这里进行其他配置,如Bean定义等
}

2.2 标记异步方法

在需要异步执行的方法上添加@Async注解。这个方法通常应该返回void或者Future类型。

如果返回void,方法执行完成后不会返回任何结果。如果返回Future,可以在之后获取异步方法的执行结果。

例如,下面是一个简单的异步方法,它模拟了一个耗时的操作:

@Service
public class MyService {@Asyncpublic void doSomethingAsync() {try {// 模拟耗时操作,这里休眠3秒Thread.sleep(30000);System.out.println("异步方法执行完成");} catch (InterruptedException e) {e.printStackTrace();}}
}

2.3 调用异步方法

可以在其他组件(如控制器、其他服务方法等)中调用这个异步方法。调用时,方法会立即返回,而实际的操作会在后台线程中执行。

例如,在一个 Spring MVC 控制器中调用上述异步方法:

@RestController
public class MyController {@Autowiredprivate MyService myService;@GetMapping("/async")public String asyncEndpoint() {myService.doSomethingAsync();return "异步操作已启动";}
}

2.4 需要异步结果时

如果异步方法需要返回一个结果,可以将方法的返回类型定义为Future。Future接口是 Java 并发包中的一部分,用于表示一个异步计算的结果。

例如,修改前面的MyService中的方法如下:

@Async
public Future<String> doSomethingAsyncWithResult() {try {// 模拟耗时操作,这里休眠3秒Thread.sleep(3000);return new AsyncResult<>("异步方法执行结果");} catch (InterruptedException e) {e.printStackTrace();return null;}
}

然后在调用这个方法的地方,可以通过Future的get方法来获取结果:

@GetMapping("/async - result")
public String asyncResultEndpoint() {try {Future<String> futureResult = myService.doSomethingAsyncWithResult();String result = futureResult.get();return result;} catch (Exception e) {e.printStackTrace();return "获取结果出错";}
}

注意:get方法会阻塞当前线程,直到异步方法执行完成并返回结果

3、注意事项

3.1 线程池

默认情况下,Spring 使用SimpleAsyncTaskExecutor来执行异步任务,这个执行器会为每个任务创建一个新的线程。在高并发场景下,这可能导致系统资源耗尽,因为创建线程是一个比较耗费资源的操作。而且过多的线程会增加上下文切换的成本,降低系统的整体性能。

例如,假设有一个 Web 应用,大量用户同时触发带有@Async注解的方法,如果不配置线程池,可能会创建大量线程,使服务器的 CPU 和内存资源被大量占用,最终导致应用程序响应缓慢甚至崩溃。

可以通过配置自定义的线程池来优化。

例如,定义一个线程池配置类:

@Configuration
public class ThreadPoolConfig {@Bean("asyncExecutor")public Executor asyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(25);executor.setThreadNamePrefix("Async - ");executor.initialize();return executor;}
}

然后在@Async注解中指定线程池名称:

@Async("asyncExecutor")
public void doSomethingAsync() {// 方法内容
}

合理设置线程池参数

  • 核心线程数(CorePoolSize):这是线程池一直保持的线程数量,即使线程处于空闲状态也不会被销毁。应该根据应用程序的平均负载来设置。例如,如果应用程序通常需要同时处理 5 个异步任务,那么可以将核心线程数设置为 5。
  • 最大线程数(MaxPoolSize):它定义了线程池允许创建的最大线程数量。当任务队列已满且有新任务到来时,线程池会创建新线程,直到达到最大线程数。设置时要考虑系统资源限制和任务的突发情况。如果系统资源有限,不能无限制地增加线程数。
  • 任务队列容量(QueueCapacity):用于存储等待执行的任务。当线程池中的线程都在忙碌时,新任务会被放入任务队列。如果队列已满,且未达到最大线程数,才会创建新线程。队列容量的大小应该根据任务的平均处理时间和任务的产生频率来确定。

线程池的复用和管理

  • 配置好的线程池可以复用线程,提高线程的利用率。通过合理设置线程池的参数,可以使线程在任务之间高效切换,减少线程创建和销毁的开销。同时,需要注意线程池的生命周期管理,在应用程序关闭时,应该正确地关闭线程池,以避免资源泄漏。

3.2 异常处理 

异常不会自动传播给调用者,这是使用@Async时一个容易被忽视的问题。

当异步方法抛出异常时,异常不会像同步方法那样直接传播到调用者。这是因为异步方法在另一个线程中执行,异常在这个线程中被抛出,如果不进行特殊处理,调用者可能完全不知道异步方法出现了问题。

例如,在一个业务逻辑中,调用了一个带有@Async注解的方法来更新数据库记录,若该异步方法在执行过程中抛出了SQLException,如果没有处理这个异常,调用者可能会继续执行后续的操作,认为更新操作已经成功,从而导致数据不一致等问题。

在异步方法内部处理异常,可以在异步方法内部使用try - catch块来捕获和处理异常。这样可以在异步方法内部对异常进行记录、重试或者进行一些补救措施。

例如:

@Async
public void asyncMethod() {try {// 可能会抛出异常的代码} catch (Exception e) {// 记录异常日志logger.error("异步方法出现异常", e);// 可以在这里进行重试或者其他补救措施}
}

配置全局异步异常处理机制,如果不想在每个异步方法内部都处理异常,可以实现AsyncUncaughtExceptionHandler接口来配置全局的异步异常处理机制。这个接口有一个handleUncaughtException方法,当异步方法抛出未捕获的异常时会被调用。

例如:

@Configuration
@EnableAsync
public class AppConfig implements AsyncUncaughtExceptionHandler {// 开启异步支持的配置@Overridepublic void handleUncaughtException(Throwable ex, Method method, Object... params) {// 记录异常日志logger.error("异步方法出现未捕获异常,方法名: " + method.getName(), ex);// 可以在这里进行全局的异常处理策略,如通知管理员等}
}

3.3 同类中调用异步方法

如果在一个类中,一个方法(方法 A)调用了同一个类中的另一个带有@Async注解的方法(方法 B),默认情况下@Async注解可能不会生效。

这是因为 Spring 的代理机制导致的,方法 A 直接调用方法 B 时,实际上没有通过代理对象来调用,所以不会触发异步执行。

例如,在一个Service类中:

@Service
public class MyService {@Asyncpublic void asyncMethod() {// 异步执行的代码}public void anotherMethod() {asyncMethod(); // 这种情况下,@Async可能不会生效}
}

解决方法是将方法 B 的调用通过注入的代理对象来进行。可以通过@Autowired将当前类自己注入进来,然后通过代理对象调用方法 B。

例如:

@Service
public class MyService {@Autowiredprivate MyService self;@Asyncpublic void asyncMethod() {// 异步执行的代码}public void anotherMethod() {self.asyncMethod(); // 通过代理对象调用,@Async生效}
}

3.4 循环依赖

在使用@Async时,如果涉及到循环依赖,可能会导致应用程序启动失败或者出现异常行为。因为异步方法的代理对象创建和循环依赖的解决可能会相互冲突。

例如,有两个服务类ServiceA和ServiceB,它们相互依赖并且都有@Async注解的方法。

在这种情况下,需要仔细检查依赖注入的方式和异步方法的使用,避免出现循环依赖导致的问题。可以通过调整依赖注入的顺序、使用@Lazy注解等方式来缓解循环依赖问题。


http://www.ppmy.cn/news/1562971.html

相关文章

Excel 技巧07 - 如何计算到两个日期之间的工作日数?(★)如何排除节假日计算两个日期之间的工作日数?

本文讲了如何在Excel中计算两个日期之间的工作日数&#xff0c;以及如何排除节假日计算两个日期之间的工作日数。 1&#xff0c;如何计算到两个日期之间的工作日数&#xff1f; 其实就是利用 NETWORKDAYS.INTL 函数 - weekend: 1 - 星期六&#xff0c;星期日 2&#xff0c;如…

基于Auto-Editor一键预处理音视频无声片段

在视频制作与后期处理中,长时间的无声片段往往会增加视频观看的乏味感,尤其在讲解类口播视频中,这种片段频繁出现。因此,自动识别并删除这些无声片段的需求逐渐增多。Auto-Editor是一款开源的自动化视频编辑工具,它通过自动检测音频信号,可以一键删除无声片段,大大提高视…

pytorch小记(四):pytorch中的重排操作:x.permute()

pytorch小记&#xff08;四&#xff09;&#xff1a;pytorch中的重排操作&#xff1a;x.permute&#xff08;&#xff09; 1. 初始张量 x2. 调用 permute 的原理案例分析2.1 z x.permute(0, 2, 1)2.2 z x.permute(1, 0, 2)2.3 z x.permute(1, 2, 0)2.4 z x.permute(2, 0, 1…

【JVM-1】深入解析JVM:Java虚拟机的核心原理与工作机制

Java虚拟机&#xff08;JVM&#xff0c;Java Virtual Machine&#xff09;是Java技术的核心&#xff0c;它使得Java程序能够“一次编写&#xff0c;到处运行”。无论是Java开发者还是对技术感兴趣的爱好者&#xff0c;理解JVM的工作原理都是非常重要的。本文将深入探讨JVM的核心…

CentOS 和 Ubantu你该用哪个

文章目录 **一、CentOS 和 Ubuntu 的详细介绍****1. CentOS****1.1 基本信息****1.2 特点****1.3 缺点** **2. Ubuntu****2.1 基本信息****2.2 特点****2.3 缺点** **二、CentOS 和 Ubuntu 的异同****1. 相同点****2. 不同点****3. 使用体验对比** **三、总结和选择建议** Cent…

Docker-compose Prometheus Grafana 安装

环境准备 #要在 Vim 中默认启用 set paste 和 set number&#xff0c; vim ~/.vimrc #在 .vimrc 文件中添加以下内容&#xff1a; set paste set number 安装 Docker Compose sudo curl -L "https://github.com/docker/compose/releases/download/2.31.1/docker-compos…

【Go】Go Gin框架初识(一)

1. 什么是Gin框架 Gin框架&#xff1a;是一个由 Golang 语言开发的 web 框架&#xff0c;能够极大提高开发 web 应用的效率&#xff01; 1.1 什么是web框架 web框架体系图&#xff08;前后端不分离&#xff09;如下图所示&#xff1a; 从上图中我们可以发现一个Web框架最重要…

学习华为熵减,激发组织活力

目录 为什么学习华为&#xff1f; 学习华为什么&#xff1f; 一、势&#xff1a;顺势而为&#xff0c;在风口上猪都会飞起来。 二、道&#xff1a;就是认识和利用规律层面&#xff0c;文化和制度创新就是企业经营之道。 三、法&#xff1a;就是一套价值管理的变革方法论。…