提升Spring Boot应用性能的秘密武器:揭秘@Async注解的实用技巧

这篇具有很好参考价值的文章主要介绍了提升Spring Boot应用性能的秘密武器:揭秘@Async注解的实用技巧。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

在日常业务开发中,异步编程已成为应对并发挑战和提升应用程序性能的关键策略。传统的同步编程方式,由于会阻碍主线程执行后续任务直至程序代码执行结束,不可避免地降低了程序整体效率与响应速度。因此,为克服这一瓶颈,开发者广泛采用异步编程技术,将那些可能阻塞的长时间运行任务委派至后台线程处理,从而确保主线程始终保持高效和灵敏的响应能力。

SpringBoot作为一款广受欢迎的应用开发框架,极大地简化了异步编程实践。其中,@Async注解是SpringBoot为实现异步编程提供的便捷工具之一。通过巧妙地应用@Async注解,开发者能够无缝地将方法调用转化为异步执行模式,进而增强系统的并发性能表现。

本文将深度剖析SpringBoot中的@Async注解,包括其内在原理、具体使用方法以及相关注意事项。我们将深入探讨@Async的工作机制,展示如何在实际的SpringBoot项目中有效运用该注解。

@Async的原理

SpringBoot中,@Async注解的实现原理基于Spring框架的AOP和任务执行器(Task Executor)机制。

@Async的启用

开启对异步方法的支持需要在配置类上添加@EnableAsync注解,然后就可以激活了一个Bean后处理器:AsyncConfigurationSelector,它负责自动配置AsyncConfigurer,为异步方法提供所需的线程池。

AsyncConfigurationSelector中默认使用PROXY的代理,即使用ProxyAsyncConfiguration,而ProxyAsyncConfiguration是用于配置Spring异步方法的代理模式的配置类。

当然我们还可以指定使用另外一个代理模式:AdviceMode.ASPECTJ,以便使用AspectJ来进行更高级的拦截和处理。

它继承至AbstractAsyncConfiguration,在AbstractAsyncConfiguration中配置AsyncConfigurersetConfigurers方法用于设置异步任务执行器和异常处理器。

AsyncConfigurer中提供了一种便捷的方式来配置异步方法的执行器(AsyncTaskExecutor)。通过实现AsyncConfigurer接口,可以自定义异步方法的执行策略、线程池等配置信息。默认情况下Spring会先搜索TaskExecutor类型的bean或者名字为taskExecutorExecutor类型的bean,都不存在使用SimpleAsyncTaskExecutor执行器。

public interface AsyncConfigurer {  
	/*
	* 该方法用于获取一个AsyncTaskExecutor对象,用于执行异步方法。
	* 可以在这个方法中创建并配置自定义的AsyncTaskExecutor,例如ThreadPoolTaskExecutor或SimpleAsyncTaskExecutor等。
	*/
    @Nullable  
    default Executor getAsyncExecutor() {  
       return null;  
    }  

	/*
	* 该方法用于获取一个AsyncUncaughtExceptionHandler对象,用于处理异步方法执行中未捕获的异常。如果异步方法执行过程中出现异常而没有被捕获,Spring会调用这个方法来处理异常。
	* 可以在这个方法中返回自定义的AsyncUncaughtExceptionHandler实现,以实现对异步方法异常的处理逻辑。
	*/
    @Nullable  
    default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {  
       return null;  
    }  
  
}

同时ProxyAsyncConfiguration中的AsyncAnnotationBeanPostProcessor会扫描应用上下文中的所有Bean,检查它们的方法是否标记了@Async注解。对于标记了@Async注解的方法,AsyncAnnotationBeanPostProcessor会创建一个代理对象,用于在调用该方法时启动一个新的线程或使用线程池执行该方法。这样就实现了异步执行的功能。同时它还负责处理@Async注解中的其他属性,例如设置异步方法的执行超时时间、指定线程池名称等。

异步方法注解与代理

当服务类的方法被@Async注解修饰时,Spring AOP会检测到这个注解,并利用动态代理技术为该类创建一个代理对象。其他组件通过Spring容器调用带有@Async注解的方法时,实际上是调用了代理对象的方法。

一个带有@Async注解的方法被调用时,Spring AOP会拦截这个方法调用。此时就会触发处理异步调用的核心拦截器:AsyncExecutionInterceptor。它的主要任务是将被@Async修饰的方法封装成一个Runnable或者Callable任务,并将其提交给TaskExecutor管理的线程池去执行。这个过程确保了异步方法的执行不会阻塞调用者线程。

TaskExecutor与线程池

TaskExecutor是一个接口,定义了如何执行RunnableCallable任务。SpringBoot提供了多种实现,如SimpleAsyncTaskExecutorThreadPoolTaskExecutor等。通常我们会自定义一个ThreadPoolTaskExecutor以满足特定需求,比如设置核心线程数、最大线程数、队列大小等参数,以确保异步任务能够高效并发执行。AsyncExecutionInterceptor将异步方法封装的任务提交给配置好的TaskExecutor管理的线程池执行。

异步方法执行与结果返回

异步方法的实际执行在独立的线程中进行,不阻塞调用者线程。异步方法的返回类型可以是voi 或者具有返回值,如果异步方法有返回值,那么返回类型通常应该是java.util.concurrent.Future,这样调用者可以通过Future对象来检查异步任务是否完成以及获取最终的结果。

@Async使用

SpringBoot中,使用@Async注解可以轻松地将方法标记为异步执行。下面来看一下如何在Spring Boot项目中正确地使用@Async注解,包括配置方法和注意事项。

在方法上添加@Async注解

要使用@Async注解,首先需要在要异步执行的方法上添加该注解。这样Spring就会在调用这个方法时将其封装为一个异步任务,并交给线程池执行。

@Service  
public class AsyncTaskService {  
  
    /**  
     * 通过@Async 注解表明该方法是个异步方法,  
     * @param i  
     */  
    @Async  
    public void executeAsyncTask(Integer i) {  
        System.out.println(Thread.currentThread().getName()+" 执行异步任务:" + i);  
    }
}    

启用异步功能

SpringBoot应用中,需要在配置类上添加@EnableAsync注解来启用对异步方法的支持。

@EnableAsync  
@SpringBootApplication  
public class SpringBootBaseApplication {  
  
    public static void main(String[] args) {  
       SpringApplication.run(SpringBootBaseApplication.class, args);  
    }  
}

配置线程池

默认情况下,SpringBoot会使用一个默认的线程池来执行异步任务(SimpleAsyncTaskExecutor)。但是,为了更好地控制线程池的行为,我们可以自定义ThreadPoolTaskExecutor,并通过AsyncConfigurer进行配置。

@Configuration  
public class TaskExecutorConfig implements AsyncConfigurer{  
  
  
    @Override  
    public Executor getAsyncExecutor() {  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        executor.setCorePoolSize(5);  
        executor.setQueueCapacity(25);  
        executor.setMaxPoolSize(10);  
        executor.setThreadNamePrefix("MyAsyncThread-");  
        executor.initialize();  
        return executor;  
    }
 }   

测试

@SpringBootTest  
public class SpringBootBaseApplicationTests {  
  
    @Autowired  
    private AsyncTaskService asyncTaskService;  
  
    @Test  
    public void asyncTest() {  
       for (int i = 0; i < 10; i++) {  
          asyncTaskService.executeAsyncTask(i);  
       }  
    }  
  
}

输出结果如下:

注意事项

  1. @Async必须配合@EnableAsync注解一起使用。两者缺一不可。

  2. 异步方法必须定义在Spring Bean中,因为Spring AOP是基于代理对象来实现的。假如我们把AsyncTaskService类中的@Service去掉。就不创建Bean。然后测试代码中修改为如下:

@Test  
public void asyncTest() {  
    AsyncTaskService asyncTaskService = new AsyncTaskService();  
    for (int i = 0; i < 10; i++) {  
       asyncTaskService.executeAsyncTask(i);  
    }  
}

执行结果如下:

都是主线程同步方法。

  1. 异步方法不能定义为private或static,因为Spring AOP无法拦截这些方法。我们修改AsyncTaskService类中的方法修改为private或者static,则会发生编译错误:
  1. 异步方法内部的调用不能使用this关键字,因为this关键字是代理对象的引用,会导致异步调用失效。
@Service  
public class AsyncTaskService {
	@Async  
	public void asyncMethod() {  
	    System.out.println("Async method executed in thread: " + Thread.currentThread().getName());  
	}  
	  
	  
	public void callAsyncMethod() {  
	    // 在同一个类中直接调用异步方法  
	    this.asyncMethod(); // 这里调用不会触发异步执行  
	    System.out.println("callAsyncMethod executed in thread: " + Thread.currentThread().getName());  
	}
}	
  1. @Async注解修饰的方法不能直接被同一个类中的其他方法调用。原因是Spring会在运行时生成一个代理类,调用异步方法时实际上是调用这个代理类的方法。因此,如果在同一个类中直接调用异步方法,@Async注解将不会生效,因为这样调用会绕过代理对象,导致异步执行失效。
@Service  
public class AsyncTaskService {

	@Async  
	public void asyncMethod() {  
	    System.out.println("Async method executed in thread: " + Thread.currentThread().getName());  
	}  
	  
	public void callAsyncMethod() {  
	    // 在同一个类中直接调用异步方法  
	    asyncMethod(); // 这里调用不会触发异步执行  
	    System.out.println("callAsyncMethod executed in thread: " + Thread.currentThread().getName());  
	}
}

测试代码:

@SpringBootTest  
public class SpringBootBaseApplicationTests {  
  
    @Autowired  
    private AsyncTaskService asyncTaskService;  
  
    @Test  
    public void asyncTest2(){  
       asyncTaskService.callAsyncMethod();  
    }  
  
}

执行结果如下:

  1. 不同的异步方法间不要相互调用
    异步方法间的相互调用会显著增加代码的复杂性层级,由于异步执行的本质在于即时返回并延迟完成任务,因此,嵌套或递归式的异步调用容易导致逻辑难以梳理和维护,特别是在涉及多异步操作状态追踪、顺序控制及依赖关系管理时尤为突出。

当异步方法内部进一步调用其他异步方法,并且牵涉到同步资源如锁、信号量等时,极易引发死锁问题。例如,一个线程在等待自身启动的另一个异步任务结果的同时,该任务却尝试获取第一个线程所持有的锁,如此循环等待,形成无法解开的死锁。

无节制地在异步方法内部启动新的异步任务,特别是在高并发场景下,可能导致线程池资源迅速耗尽,使得系统丧失处理更多请求的能力。此外,直接的异步方法调用还增加了错误处理与日志记录的难度,特别是遇到异常情况时,往往难以追溯原始调用链路以精准定位问题源头。

若需要确保异步方法按照特定顺序执行,直接调用会导致逻辑混乱不清。为解决这一问题,通常推荐采用回调机制、Future/CompletionStage链式编程、响应式编程模型(如RxJava、Project Reactor)等方式来确保有序执行并降低耦合度。

同时,频繁且低延迟的任务间直接互相调用可能会引入额外的上下文切换开销,从而对系统的整体性能造成潜在负面影响。

  1. 合理配置线程池
    Spring Boot默认提供的线程池配置可能无法充分满足特定应用在复杂多变生产环境下的需求,例如其预设的线程数、队列大小和拒绝策略等参数可能不尽合理。为确保资源的有效管理和精细控制,我们可以通过自定义线程池来灵活设定核心线程数、最大线程数、线程空闲超时时间、任务等待队列容量以及饱和策略(如任务拒绝策略)等关键属性,从而适应不同业务场景对并发执行任务数量及资源消耗的精准调控。

另外,不同类型异步任务具有不同的执行特性:有的任务耗时较长,而有的则短促且频繁。针对这种情况,为各类任务配置独立的线程池有助于实现更好的资源隔离,避免任务间的相互影响,进而保障系统的稳定性和响应速度。同时,为了满足特定的安全规范或性能要求,自定义线程池还可以支持诸如设置守护线程、优先级、线程命名格式化等功能。

更重要的是,自定义线程池有利于系统内部执行状态的深度监控与问题诊断。通过制定合理的命名规则、详尽的日志记录以及精确的metrics统计分析,我们可以清晰洞察每个线程池的工作状况,及时发现并优化潜在的性能瓶颈。

如果不进行自定义线程池配置,仅依赖于默认或简化的线程池实现,在面对大量涌入的任务时,可能会因线程资源耗尽导致整个系统响应能力和可用性受损。因此,采用合理配置的自定义线程池能够在高负载环境下有效防范此类风险,有力支撑系统的稳健运行。

@Configuration  
public class TaskExecutorConfig implements AsyncConfigurer{  
  
  
    @Override  
    public Executor getAsyncExecutor() {  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        // 核心线程数  
        executor.setCorePoolSize(5);  
        // 设置队列容量  
        executor.setQueueCapacity(25);  
        // 最大线程数  
        executor.setMaxPoolSize(10);  
        // 自定义线程名称前缀  
        executor.setThreadNamePrefix("MyAsyncThread-");  
        executor.initialize();  
        return executor;  
    }
}    

关于自定义线程池的参数讲解请参考我这篇文章:重温Java基础(二)之Java线程池最全详解

  1. 异常处理:
    异步方法内部的异常通常不会被调用方捕获到,因此需要在异步方法内部进行异常处理,可以通过try-catch块:
@Async  
public void asyncMethod() {  
    try {  
        // 异步操作代码  
    } catch (Exception ex) {  
        log.error("Error occurred in async method", ex);  
        // 其他错误处理逻辑  
    }  
}

或者使用@Async注解的exceptionHandler属性来处理异常并进行适当的日志记录或错误处理。

@Configuration  
public class TaskExecutorConfig implements AsyncConfigurer{

	@Override  
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {  
        return new CustomAsyncExceptionHandler();  
    }  
  
}  

// 自定义异常处理器
class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {  
  
    @Override  
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {  
        log.error("Uncaught exception in async method: " + method.getName(), ex);  
        // 其他错误处理逻辑  
    }  
}
  1. 与事务的交互
    默认情况下,当我们在Spring应用中使用@Async注解标记一个方法为异步执行时,这个方法将不会参与到其调用者所处的事务上下文中。这意味着,如果调用异步方法的方法在一个事务内执行,该事务将在调用异步方法后正常提交或回滚,但异步方法内部的操作并不会受到这个事务的影响。

例如,若在同步方法中修改了数据库记录,并随后调用了一个异步方法来更新其他相关的数据,那么如果同步方法中的事务在调用异步方法后提交,而异步方法在执行过程中抛出了异常导致更新失败,这时第一部分已提交的数据和第二部分未成功更新的数据之间就会产生不一致的情况。

为了确保异步方法能够正确地参与事务管理,可以通过设置@Async注解的事务传播行为属性(@Transactionalpropagation属性值)来解决这个问题。

@Transactional(propagation = Propagation.REQUIRES_NEW)  
@Async  
public void asyncMethod() {  
    System.out.println("Async method executed in thread: " + Thread.currentThread().getName());  
    // 具体业务
}

这里通过设置Propagation.REQUIRES_NEW,指示Spring在执行异步方法时开启一个新的、与当前事务无关的事务。这样即使异步方法内部发生异常,它自己的事务会独立进行提交或回滚,从而保证了数据的一致性。不过要注意的是,这种做法可能会增加系统资源消耗,因为每次异步任务都会创建新的事务上下文。

总结

通过本文的介绍,我们了解了SpringBoot@Async注解的原理、使用方法以及需要注意的事项。

@Async注解能够将方法标记为异步执行,利用了Spring框架的AOP和任务执行器机制,使得异步方法能够在后台线程池中并发执行,提高系统的并发能力和响应性。

然而,在使用@Async注解时,需要注意避免异步方法之间相互调用,合理配置线程池,进行异常处理,处理上下文丢失以及与事务的正确交互。这些注意事项能够确保异步方法的可靠性和稳定性,提高应用程序的性能和可维护性。

总的来说,@Async注解是SpringBoot中用于实现异步方法的重要特性,能够有效地提升应用程序的性能和并发能力,但在使用时需要谨慎考虑其使用场景和注意事项,以充分发挥其优势。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等文章来源地址https://www.toymoban.com/news/detail-838486.html

到了这里,关于提升Spring Boot应用性能的秘密武器:揭秘@Async注解的实用技巧的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 详解数据库分片,大幅提升Spring Boot查询MySQL性能

    微服务项目中通常包含各种服务。其中一项服务与存储用户相关的数据有关。我们使用Spring Boot作为后端,使用MySQL数据库。 随着用户基数的增长,服务性能受到了影响,延迟也上升了。由于只有一个数据库和一张表,许多查询和更新由于锁异常返回错误。此外,随着数据库

    2024年01月16日
    浏览(58)
  • Python中的迭代器与生成器提高性能的秘密武器【第143篇—迭代器与生成器】

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在Python编程中,迭代器和生成器是提高性能和减少内存消耗的重要工具。它们不仅简化了代码结构,而且在处理大型数据集时具有明显的优势

    2024年03月24日
    浏览(56)
  • AI大模型探索之路-应用篇2:Langchain框架ModelIO模块—数据交互的秘密武器

    目录 前言 一、概述 二、Model 三、Prompt 五、Output Parsers 总结 随着人工智能技术的不断进步,大模型的应用场景越来越广泛。LangChain框架作为一个创新的解决方案,专为处理大型语言模型的输入输出而设计。其中,Model IO(输入输出)模块扮演着至关重要的角色,负责构建和管

    2024年04月13日
    浏览(40)
  • 深入理解 HTTP/2:提升 Web 性能的秘密

    HTTP/2 是一项重大的网络协议升级,旨在提升 Web 页面加载速度和性能。在这篇博客中,我们将深入探讨 HTTP/2 的核心概念以及如何使用它来加速网站。 HTTP/2 是 HTTP 协议的下一个版本,旨在解决 HTTP/1.1 中的性能瓶颈问题。它引入了多路复用、二进制协议、首部压缩等新特性,

    2024年02月12日
    浏览(51)
  • 解锁文字魔法:探索自然语言处理的秘密——从技术揭秘到应用实战!

    目录  前言 关键技术——揭密自然语言处理的秘密武器! 领域应用——自然语言处理技术在不同领域的奇妙表演! 超越极限——自然语言处理技术面临的顽强挑战揭秘! 科技VS伦理——自然语言处理技术的发展与伦理社会的纠结较量! 开启应用奇迹!实战自然语言处理技术

    2024年01月19日
    浏览(73)
  • Spring Boot与Netty:构建高性能的网络应用

    点击下载《Spring Boot与Netty:构建高性能的网络应用》 本文将详细探讨如何在Spring Boot应用中集成Netty,以构建高性能的网络应用。我们将首先了解Netty的原理和优势,然后介绍如何在Spring Boot项目中集成Netty,包括详细的使用流程和步骤,以及带有注释的代码示例。通过本文,

    2024年02月22日
    浏览(58)
  • Spring Boot深度解析:快速开发的秘密

    🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通Golang(基础

    2024年02月09日
    浏览(38)
  • 【稳定性】秘密武器--功能开关技术

    继上篇【稳定性:关于缩短MTTR的探索】后,看到一些线上问题应急预案采用的是回滚方案, 但是在大部分牵扯代码场景下,开关技术才是线上问题快速止血的最佳方式 。比如履约平台组的Promise作为下单黄金链路,如遇线上问题的话, 采用通用的回滚方式需要5-10+分钟(500+台

    2024年02月08日
    浏览(57)
  • 【译】梵文:人工智能的秘密武器?

    原作:阿曼班迪 / 安维沙 /  引言:你知道人工智能可以更有效地学习梵语吗? /机器翻译/ 梵语是世界上最古老、最复杂的语言之一。它拥有丰富的文学和哲学传统,是印度及其他地区许多现代语言的根源。但你知道梵文也是一门非常适合人工智能(AI)的语言吗? 人工智能

    2024年03月21日
    浏览(52)
  • JIRA:项目管理的秘密武器

    在当今动态且快速变化的商业环境中,项目管理已经成为任何组织成功的关键因素。能够有效地管理项目,保证项目在设定的时间和预算内按照预期的质量完成,是每个项目经理的目标。为了实现这个目标,项目经理需要依赖强大的工具,而JIRA就是这样一个工具。 JIRA是Atl

    2024年02月12日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包