首页 > 其他分享 >SpringBoot @Async:魔法和陷阱

SpringBoot @Async:魔法和陷阱

时间:2023-11-11 22:32:02浏览次数:52  
标签:SpringBoot void 魔法 线程 executor Async 方法 public

来源:https://medium.com/

@Async注解就像是springboot项目中性能优化的秘密武器。是的,我们也可以手动创建自己的执行器和线程池,但@Async使事情变得更简单、更神奇。

@Async注释 允许我们在后台运行代码,因此我们的主线程可以继续运行,而无需等待较慢的任务完成。但是,就像所有秘密武器一样,明智地使用它并了解它的局限性非常重要。

正文

在这篇文章中,我们将深入探讨@Async 的魔力以及在 Spring Boot 项目中使用它时应该注意的问题。首先让我们学习如何在应用程序中使用 @Async 的基础知识。

我们需要在 Spring Boot 应用程序中启用@Async 。为此,我们需要将@EnableAsync注释添加到配置类或主应用程序文件中。这将为应用程序中使用@Async注释的所有方法启用异步行为。

@SpringBootApplication
@EnableAsync
public class BackendAsjApplication {
}

我们还需要创建一个 Bean,指定使用 @Async 注释的方法的配置。我们可以设置最大线程池大小、队列大小等。不过,添加这些配置时要小心。否则,我们可能很快就会耗尽内存。我通常还会添加一个日志,以在队列大小已满并且没有更多线程来接收新传入任务时发出警告。

@Bean
 public ThreadPoolTaskExecutor taskExecutor() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(2);
  executor.setMaxPoolSize(2);
  executor.setQueueCapacity(500);
  executor.setThreadNamePrefix("MyAsyncThread-");
  executor.setRejectedExecutionHandler((r, executor1) -> log.warn("Task rejected, thread pool is full and queue is also full"));
  executor.initialize();
  return executor;
 }

现在,让我们使用它。假设我们有一个服务类,其中包含我们想要异步的方法。我们将使用@Async注释此方法。

@Service
public class EmailService {
    @Async
    public void sendEmail() {
   
    }
}

在代码示例中,你会看到多次提到EmailService和PurchaseService。这些只是示例。我不想将所有内容都命名为“MyService”。因此,将其命名为更有意义的名称。在电子商务应用程序中,你当然希望你的 EmailService 是异步的,这样客户请求就不会被阻止

现在,当我们调用此方法时,它将立即返回,从而释放调用线程(通常是主线程)以继续执行其他任务。该方法将继续在后台执行,稍后将结果返回给调用线程。由于我们在这里用 void 标记了 @Async 方法,因此我们对它何时完成并不真正感兴趣。

非常简单而且非常强大,对吧?(当然,我们可以做更多配置,但上面的代码足以运行完全异步的任务)

但是,在我们开始使用 @Async 注释所有方法之前,我们需要注意一些问题。

@Async方法需要位于不同的类中

使用 @Async 注释时,请务必注意,我们不能从同一类中调用 @Async 方法。这是因为这样做会导致无限循环并导致应用程序挂起。

以下是不应该做的事情的示例:

@Service
public class PurchaseService {

    public void purchase(){
        sendEmail();
    }

    @Async
    public void sendEmail(){
        // Asynchronous code
    }
}

相反,我们应该为异步方法使用单独的类或服务。

@Service
public class EmailService {

    @Async
    public void sendEmail(){
        // Asynchronous code
    }
}

@Service
public class PurchaseService {

    public void purchase(){
        emailService.sendEmail();
    }

    @Autowired
    private EmailService emailService;
}

现在你可能想知道,我可以从另一个异步方法中调用异步方法吗?最简洁的答案是不。当调用异步方法时,它会在不同的线程中执行,并且调用线程会继续执行下一个任务。如果调用线程本身是异步方法,则它无法等待被调用的异步方法完成后再继续,这可能会导致意外行为。

@Async 和 @Transcational 配合不佳

@Transactional 注释用于指示方法或类应该参与事务。它用于确保一组数据库操作作为单个工作单元执行,并且在发生任何故障时数据库保持一致状态。

当一个方法被@Transactional注解时,Spring会在该方法周围创建一个代理,并且该方法内的所有数据库操作都在事务上下文中执行。Spring 还负责在调用方法之前启动事务,并在方法返回后提交事务,或者在发生异常时回滚事务。

但是,当你使用 @Async 注释使方法异步时,该方法将在与主应用程序线程不同的单独线程中执行。这意味着该方法不再在 Spring 启动的事务上下文中执行。因此,@Async方法内的数据库操作不会参与事务,并且在出现异常时数据库可能会处于不一致的状态。

@Service
public class EmailService {

    @Transactional
    public void transactionalMethod() {
        //database operation 1
        asyncMethod();
        //database operation 2
    }

    @Async
    public void asyncMethod() {
        //database operation 3
    }
}

在此示例中,数据库操作 1 和数据库操作 2 在 Spring 启动的事务上下文中执行。但是,数据库操作 3 是在单独的线程中执行的,并且不是事务的一部分。

因此,如果在执行数据库操作3之前发生异常,则数据库操作1和数据库操作2将按预期回滚,但数据库操作3不会回滚。这可能会使数据库处于不一致的状态。

当然,有很多方法可以解决这个问题,即使用 TransactionTemplate 之类的东西来管理事务,但开箱即用,如果从转换方法调用异步方法,最终会出现问题。

@Async 阻塞问题

假设这是我们的 @Async 线程池的配置:

@Bean
 public ThreadPoolTaskExecutor taskExecutor() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(2);
  executor.setMaxPoolSize(2);
  executor.setQueueCapacity(500);
  executor.setThreadNamePrefix("MyAsyncThread-");
  executor.setRejectedExecutionHandler((r, executor1) -> log.warn("Task rejected, thread pool is full and queue is also full"));
  executor.initialize();
  return executor;
 }

这意味着在任何特定时刻,我们最多将运行 2 个 @Async 任务。如果有更多任务进来,它们将排队,直到队列大小达到 500。

但现在假设,我们的 @Async 任务之一执行起来花费了太多时间,或者只是由于外部依赖而被阻止。这意味着所有其他任务将排队并且执行速度不够快。根据你的应用程序类型,这可能会导致延迟。

解决此问题的一种方法是为长时间运行的任务使用单独的线程池,为更紧急且不需要大量处理时间的任务使用单独的线程池。我们可以这样做:

@Primary
 @Bean(name = "taskExecutorDefault")
 public ThreadPoolTaskExecutor taskExecutorDefault() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(2);
  executor.setMaxPoolSize(2);
  executor.setQueueCapacity(500);
  executor.setThreadNamePrefix("Async-1-");
  executor.initialize();
  return executor;
 }

 @Bean(name = "taskExecutorForHeavyTasks")
 public ThreadPoolTaskExecutor taskExecutorRegistration() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(2);
  executor.setMaxPoolSize(2);
  executor.setQueueCapacity(500);
  executor.setThreadNamePrefix("Async2-");
  executor.initialize();
  return executor;
 }

然后要使用它,只需在 @Async 声明中添加执行器的名称即可:

@Service
public class EmailService {
    @Async("taskExecutorForHeavyTasks")
    public void sendEmailHeavy() {
        //method implementation
    }
}

但是,请注意,我们不应该在调用Thread.sleep()或的方法上使用@Async Object.wait(),因为它会阻塞线程,并且使用@Async的目的将落空。

@Async 中的异常

SpringBoot @Async:魔法和陷阱_Async

另一件需要记住的事情是 @Async 方法不会向调用线程抛出异常。这意味着你需要在 @Async 方法中正确处理异常,否则它们将丢失。

以下是不应该做的事情的示例:

@Service
public class EmailService {

    @Async
    public void sendEmail() throws Exception{
        throw new Exception("Oops, cannot send email!");
    }
}

@Service
public class PurchaseService {
    
    @Autowired
    private EmailService emailService;

    public void purchase(){
        try{
            emailService.sendEmail();
        }catch (Exception e){
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

在上面的代码中,异常在asyncMethod()中抛出,但不会被调用线程捕获,并且 catch 块不会被执行。

为了正确处理 @Async 方法中的异常,我们可以结合使用 Future 和 try-catch 块。这是一个例子:

@Service
public class EmailService {

    @Async
    public Future<String> sendEmail() throws Exception{
        throw new Exception("Oops, cannot send email!");
    }
}

@Service
public class PurchaseService {

    @Autowired
    private EmailService emailService;

    public void purchase(){
        try{
            Future<String> future = emailService.sendEmail();
            String result = future.get();
            System.out.println("Result: " + result);
        }catch (Exception e){
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

通过返回 Future 对象并使用 try-catch 块,我们可以正确处理和捕获 @Async 方法中引发的异常。

总之,Spring Boot中的@Async注释是提高应用程序性能和可伸缩性的强大工具。但是,小心使用它并注意它的局限性是很重要的。通过理解这些陷阱并使用CompletableFuture和Executor等技术,你可以充分利用@Async注释并将应用程序提升到下一个级别。


标签:SpringBoot,void,魔法,线程,executor,Async,方法,public
From: https://blog.51cto.com/u_6813689/8320768

相关文章

  • [THUSCH2017] 大魔法师
    前期准备1.熟练的掌握区间修改线段树2.对矩阵乘法有部分的了解,知道如何使用3.对卡常十分精通题目大意题目给定\(n\)个三元组,每个三元组包含\(A\)、\(B\)、\(C\)三个元素,一共进行\(m\)次操作,分别是下面七种之一:1.令给定区间内,\(A_i=A_i+B_i\)2.令给定区间内,\(B_i=B......
  • 基于SpringBoot的来访管理系统的设计与实现-计算机毕业设计源码+LW文档
    摘 要本文首先实现了来访管理技术的发展,随后依照传统的软件开发流程,最先为系统挑选适用的言语和软件开发平台,依据需求分析开展控制模块制作和数据库查询构造设计,依据系统整体功能模块的设计,制作系统的功能模块图、流程表和E-R图。其次进行设计框架,依据设计的框架撰写编码,完成系......
  • 基于springboot的旅游出行指南-计算机毕业设计源码+LW文档
    摘 要随着社会的发展,旅游出行的管理形势越来越严峻。越来越多的用户利用互联网获得信息,但旅游出行信息鱼龙混杂,信息真假难以辨别。为了方便用户更好的获得本旅游出行信息,因此,设计一种安全高效的旅游出行指南极为重要。为设计一个安全便捷,并且使用户更好获取本旅游出行信息,本文......
  • springboot学习日记(二)
    运行springboot项目报错o.s.b.d.LoggingFailureAnalysisReporter,查资料试着查一下端口占用8080。netstat-aon|findstr8080发现8080端口被进程8768占用。 查找8768进程的程序tasklist|findstr8768发现是腾讯会议。。。 退出了再试试,还是没解决问题。。很好,排除一......
  • SpringBoot整合Ajax
    使用springboot整合ajax实现登录验证及查询信息。添加jar依赖<dependencies><!--<dependency>--><!--<groupId>org.aspectj</groupId>--><!--<artifactId>aspectjweaver</artifactId>-->......
  • 关于asyncio.create_task异步并发执行的研究
    关于asyncio.create_task异步并发执行的研究#不在乎结果版本asyncdefdo_some_thing(a,b):time.sleep(3)print(f"{datetime.datetime.now()}handledo_some_thingwitha:{a}andb:{b}")returna+bclassTaskHandler(tornado.web.RequestHandler):......
  • SpringBoot项目集成ElasticSearch服务
    目录版本介绍背景介绍优势说明集成过程1.引入依赖2.添加配置文件3.初始化示例说明代码结果总结提升版本介绍  Springboot的版本是:2.3.12  ElasticSearch的版本是:7.6.2背景介绍  在我们的项目中经常会遇到对于字符串的一些操作,例如对于字符串的分词,通过一个词去查找对应......
  • 博客管理系统|基于SpringBoot+Vue+ElementUI付费博客系统的设计与实现
    作者主页:编程指南针作者简介:Java领域优质创作者、博客专家、特邀作者、多年架构师设计经验、腾讯课堂常驻讲师主要内容:Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助收藏点赞不迷路 关注作者有好处项目编号:BS-PT-089二,环境介绍语言环境:Java: jdk1.8数据库:Mysql......
  • 多模块springboot项目打jar包 没有主清单属性
    说明:一个多模块的项目 两个子Module 一个core 一个server。java8的环境 |--XChome(pom.xmlxchome的 父级)--|--xc-core(pom.xmlxc-core的子级)--|--xc-server(pom.xmlxc-server的子级) xc-core:主要定义一些常量类、工具类、业务部分(controllerservice......
  • 基于SSM+SpringBoot影院在线预订系统开发与设计(附源码资料)
    文章目录1.项目简介2.项目运行截图2.1.电影网站首页2.2.电影列表2.3.反馈与意见2.4.电影详情2.5.电影预订2.6.会员登录2.7.会员注册2.8.管理员后台首页2.9.用户管理2.10.电影管理2.11.房间管理2.12.房间管理2.13.黑名单管理2.14.用户反馈管理2.15.预约管理3.源码获取1.项目简介该......