深入理解Spring的@Async注解:实现异步方法调用

这篇具有很好参考价值的文章主要介绍了深入理解Spring的@Async注解:实现异步方法调用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

在当今高速发展的应用开发领域,对于提升系统性能和响应能力的需求越来越迫切。而异步编程作为一种解决方案,已经成为现代应用开发中的一项重要技术。本篇博客将带您深入探究 Java 中的 @Async 注解,揭示其强大的异步执行能力和精妙的实现机制。

异步编程是一种编程模式,通过将任务分解为多个子任务,并在后台或并行线程中执行这些子任务,以提高程序的性能和响应能力。

@Async 注解简介

@Async 注解是 Spring 框架提供的注解,用于将方法标记为异步执行的方法。它的作用是告诉 Spring 框架在调用被注解的方法时,将其放入线程池中异步执行,而不是阻塞等待方法的完成。

@Async 注解的工作原理是,在调用被注解的方法时,Spring 会将该方法的执行转移到线程池中的一个线程进行处理。执行完成后,方法的返回值将通过 Future 或 CompletableFuture 进行封装,以便获取方法的返回结果。

  1. @Async 注解适用于以下场景,并具有以下优势:

    • 网络请求:在处理网络请求时,可以使用 @Async 注解将请求发送和响应处理分离,提高系统的并发处理能力。
    • 耗时计算:对于需要耗费大量时间的计算任务,可以使用 @Async 注解将计算过程放在后台执行,避免阻塞主线程,提高系统的响应速度。
    • 并行处理:通过 @Async 注解,可以同时执行多个任务,将多个相互独立的任务并行处理,从而减少整体处理时间。
    • 响应能力提升:使用异步编程可以避免阻塞主线程,提高系统的并发能力和响应能力,增强用户体验。
    • 代码简化:使用 @Async 注解可以简化编程模型,将异步执行的逻辑与业务逻辑分离,使代码更清晰、易于维护。

    异步执行通过将任务分解为多个并发执行的子任务,可以充分利用系统资源,提高系统的吞吐量和并发处理能力,从而提升系统的性能和响应能力。@Async 注解简化了异步编程的实现,使开发人员能够更方便地使用异步处理机制。同时,它还可以使代码更易于阅读和维护,提高开发效率。

@Async 注解的源码解析

@Async 注解在 Spring 框架中的实现主要依赖于以下几个关键组件:

  • AsyncAnnotationBeanPostProcessor:这是一个 Bean 后置处理器,负责解析带有 @Async 注解的方法,将其包装成异步任务。
  • AsyncTaskExecutor:这是一个任务执行器,用于执行异步任务。可以通过配置来指定具体的线程池或任务调度器。
  • AsyncConfigurer:这是一个可选的接口,用于提供自定义的异步任务执行器。

在 Spring 框架中,当启用异步支持时,AsyncAnnotationBeanPostProcessor 会扫描容器中的 Bean,并检查其中的方法是否标记有 @Async 注解。如果发现带有 @Async 注解的方法,它将会将其封装成一个代理对象,并注册为一个可执行的异步任务。

当调用被 @Async 注解标记的方法时,实际上是调用了该方法的代理对象。代理对象会将方法的执行转移到线程池中的一个线程进行处理,并返回一个 Future 对象,用于获取方法的返回结果。

线程池的配置可以通过 Spring 的配置文件或编程方式进行指定。可以配置线程池的大小、线程池的类型(如固定大小线程池、缓存线程池等)以及任务调度策略等。

异步方法与事务的关系

在使用 @Async 注解标记的异步方法与事务之间存在一些关系和注意事项。

  1. 默认情况下,异步方法不受事务管理的影响。当一个带有 @Transactional 注解的方法调用一个标记为 @Async 的异步方法时,异步方法将在一个新的线程中执行,与原始方法的事务无关。
  2. 异步方法独立事务。如果希望异步方法能够参与到事务管理中,可以使用 Propagation.REQUIRES_NEW 传播行为。将异步方法设置为 @Transactional(propagation = Propagation.REQUIRES_NEW) ,这样异步方法将在新的事务中执行,与原始方法的事务隔离开来。
  3. 异步方法和事务的提交。由于异步方法是在独立的线程中执行的,与原始方法的事务是分离的。因此,异步方法中的事务提交操作不会对原始方法的事务产生影响。即使异步方法中的事务提交失败,也不会导致原始方法的事务回滚。
  4. 异步方法和事务的异常处理。异步方法中的异常默认是不会被捕获和处理的,除非在异步方法中显式地进行了异常处理。如果需要对异步方法中的异常进行处理,可以使用 AsyncUncaughtExceptionHandler 接口来自定义异常处理逻辑。

需要注意的是,使用异步方法与事务的组合可能会带来一些潜在的问题和风险,如数据不一致性、并发冲突等。在使用异步方法和事务的同时,需要仔细考虑业务需求和数据一致性的要求,确保逻辑正确性和数据完整性。

总结起来,异步方法和事务之间的关系可以通过设置事务的传播行为来调整。默认情况下,异步方法是独立于事务的,可以通过设置 Propagation.REQUIRES_NEW 传播行为使异步方法参与到事务管理中。然而,需要注意并发和数据一致性的问题,并根据具体业务需求合理使用异步方法和事务的组合。

Async异常处理

在使用 @Async进行异步方法调用时,异常处理是一个重要的方面。以下是异步方法的异常处理机制:

  1. 默认情况下,异步方法的异常会被捕获并封装为Future对象(或CompletableFuture对象)。您可以通过Future.get() 方法或CompletableFuture.get() 方法获取异步任务的结果,并在调用时捕获异常。如果异步任务抛出异常,将会在调用get() 方法时重新抛出异常,您可以在调用端进行异常处理。
@Async
public CompletableFuture<String> performTask() {
    // 异步任务逻辑
}

// 调用异步方法并处理异常
CompletableFuture<String> future = myService.performTask();
try {
    String result = future.get();
    // 处理正常结果
} catch (InterruptedException | ExecutionException e) {
    // 处理异常情况
}
  1. 您还可以使用AsyncUncaughtExceptionHandler接口来处理异步方法中未捕获的异常。通过实现AsyncUncaughtExceptionHandler接口,并在AsyncConfigurer中重写getAsyncUncaughtExceptionHandler() 方法,您可以定义全局的异步异常处理逻辑。
@Configuration
    @EnableAsync
    public class AsyncConfig implements AsyncConfigurer {

        // 配置异步方法执行器
        @Override
        public Executor getAsyncExecutor() {
            // 配置任务执行器
        }

        // 配置异步方法未捕获异常处理器
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new CustomAsyncExceptionHandler();
        }

        // 其他配置...
    }
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // 处理异步方法未捕获的异常
        Class<?> clazz = method.getDeclaringClass();
        String message = String.format("异步方法执行失败,具体类名: %s, 方法名:%s, 异常信息: %s", clazz.getName(), method.getName(), ex);
        log.error("异步方法执行失败,具体类名: {}, 方法名:{}, 方法入参:{}, 异常信息: {}", clazz.getName(), method.getName(), Arrays.toString(params), ex.getMessage(), ex);
    }
}

在上述示例中,CustomAsyncExceptionHandler实现了AsyncUncaughtExceptionHandler接口,并实现了handleUncaughtException() 方法来处理异步方法中未捕获的异常。您可以在该方法中编写自定义的异常处理逻辑,例如日志记录、错误报警等。

通过上述异常处理机制,您可以捕获和处理异步方法中的异常,从而确保对异步任务的异常情况进行适当的处理。

ThreadLocal和Async使用问题

在工作过程中,经常遇到这个问题,系统通常会通过拦截器获取用户信息并设置到ThreadLoacl中,但是在异步方法中获取用户信息,却出现了获取到了其他用户信息的问题。
这是因为@Async注解会在异步执行方法时切换线程,而线程切换会导致ThreadLocal中的内容无法被正确传递。

解决这个问题的一种方法是使用AsyncTaskExecutor的子类,例如ThreadPoolTaskExecutor,并在配置中设置TaskDecoratorTaskDecorator可以在每次异步任务执行时对线程进行修饰,以确保ThreadLocal中的内容被正确传递。

以下是一个示例配置的代码片段:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setTaskDecorator(new ThreadLocalTaskDecorator()); // 设置TaskDecorator
        executor.initialize();
        return executor;
    }

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

    // 自定义的TaskDecorator
    private static class ThreadLocalTaskDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            // 保存当前线程的ThreadLocal内容
             // 获取调用线程的 traceId 和用户信息
            String traceId = ThreadLocalUtils.getTraceId();
            User user = ThreadLocalUtils.getUser();
            return () -> {
                try {
                    // 恢复之前保存的ThreadLocal内容
                     // 在子线程中设置 traceId 和用户信息
                    ThreadLocalUtils.setTraceId(traceId);
                    ThreadLocalUtils.setUser(user);
                    runnable.run();
                } finally {
                   // 清除子线程的 traceId 和用户信息
                   ThreadLocalUtils.clear();
                }
            };
        }
    }
}

在上述示例中,我们使用ThreadLocalContextHolder类来管理ThreadLocal的操作,包括设置、获取和清理ThreadLocal中的内容。

public class ThreadLocalUtils {
    private static final ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
    private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();

    public static String getTraceId() {
        return traceIdThreadLocal.get();
    }

    public static void setTraceId(String traceId) {
        traceIdThreadLocal.set(traceId);
    }

    public static User getUser() {
        return userThreadLocal.get();
    }

    public static void setUser(User user) {
        userThreadLocal.set(user);
    }

    public static void clear() {
        traceIdThreadLocal.remove();
        userThreadLocal.remove();
    }
}

通过使用以上的配置和ThreadLocalTaskDecorator,你可以确保在异步执行时,ThreadLocal中的用户信息能够正确传递并被获取到。

多线程池配置

如果您需要配置多个不同类型的 @Async注解,并且使用不同的线程池类型(缓存线程池和固定线程池),可以按照以下方式进行配置:
首先,创建多个线程池和相应的TaskExecutor bean。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    // 缓存线程池
    @Bean("cachedThreadPool")
    public TaskExecutor cachedThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(0); // 根据实际情况调整核心线程数
        executor.setMaxPoolSize(Integer.MAX_VALUE); // 根据实际情况调整最大线程数
        executor.setQueueCapacity(100); // 根据实际情况调整队列容量
        executor.setThreadNamePrefix("cached-thread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setTaskDecorator(new MdcTaskDecorator()); // 设置任务装饰器
        executor.initialize();
        return executor;
    }

    // 固定线程池
    @Bean("fixedThreadPool")
    public TaskExecutor fixedThreadPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // 根据实际情况调整核心线程数
        executor.setMaxPoolSize(10); // 根据实际情况调整最大线程数
        executor.setQueueCapacity(0); // 不使用队列,直接执行
        executor.setThreadNamePrefix("fixed-thread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setTaskDecorator(new MdcTaskDecorator()); // 设置任务装饰器
        executor.initialize();
        return executor;
    }

    // 配置异步方法执行器
    @Override
    public Executor getAsyncExecutor() {
        return cachedThreadPool();
    }

    // 配置自定义的异步方法执行器,用于特定类型的异步任务
    @Bean("customAsyncExecutor")
    public Executor customAsyncExecutor() {
        return fixedThreadPool();
    }

    // 其他配置...

}

在上述示例中,我们创建了两个不同类型的线程池:cachedThreadPoolfixedThreadPool,并将它们作为TaskExecutor bean 注册到Spring容器中。

通过以上配置,您可以使用不同的线程池类型为不同类型的异步任务配置不同的执行器,并根据需求调整线程池的属性。

最佳实践和注意事项

在使用异步方法时,需要注意以下几点:

  • 异步方法应尽量保持简单和独立,不涉及复杂的事务逻辑。
  • 异步方法的执行时间应控制在合理的范围内,避免因长时间执行导致线程资源占用过多。
  • 需要考虑异步方法与其他业务逻辑的协调,确保异步方法的执行顺序和结果正确性。
  • 异步方法的并发性可能导致资源竞争和并发访问的问题,需要进行适当的并发控制和线程安全处理。

github: https://github.com/kong0827
博客: https://juejin.cn/user/3403743731154190/posts文章来源地址https://www.toymoban.com/news/detail-753006.html

到了这里,关于深入理解Spring的@Async注解:实现异步方法调用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • java小技能:spring中的异步方法@Async失效的原因

    异步执行的场景:不处理方法结果/在不关心方法执行结果时经常需要异步执行 需求:自动报名活动、批量导入报名活动

    2024年02月09日
    浏览(97)
  • SpringBoot中异步注解@Async介绍

    介绍在SpringBoot项目中,使用@Async不生效的原因介绍和分析; 代码参考gitee仓库:spring-boot-2022-05: 主要是介绍Spring框架注解、常用的功能的使用案例,记录平时遇到的技术知识点,进行实践操作; - Gitee.com 1.启动类中没有添加注解@EnableAsync; 2.同一个类中调用含有@Async的方法;因

    2023年04月17日
    浏览(37)
  • 深入理解Spring @RequestBody注解的用法与作用

    当我们使用 Spring Boot 框架处理客户端提交的 HTTP 请求时,常常需要获取请求参数并将其转换为相应的 Java 对象。@RequestBody 注解可以帮助我们实现这个目标,它用于从 HTTP 请求体中获取请求参数,并将其转换为指定的 Java 对象。本篇博客将介绍 @RequestBody 注解的基本用法和注意

    2024年02月15日
    浏览(46)
  • 深入理解Spring AOP注解:@DataScope与@DataSource

    在Spring AOP中,注解的使用扮演着重要角色,它们可以帮助我们明确定义切面、切点和增强处理。在本文中,我们将深入探讨RuoYi框架两个自定义注解:@DataScope和@DataSource。 定义注解 首先,我们定义一个名为@DataScope的注解,用于在特定的方法上实现数据范围过滤等功能。 定义

    2024年02月16日
    浏览(34)
  • 深入理解 Spring 中的 @RequestBody 和 @ResponseBody 注解及其区别

    在现代的 Web 开发中,处理 HTTP 请求和响应是不可或缺的任务。Spring Framework 提供了丰富的功能来简化这些任务,并使开发人员能够更专注于业务逻辑。在本文中,我们将深入探讨 Spring 中的 @RequestBody 和 @ResponseBody 注解,以及它们之间的区别。 @RequestBody 注解是 Spring 提供的一

    2024年02月14日
    浏览(42)
  • 深入理解Spring Kafka中@KafkaListener注解的参数与使用方式

    Apache Kafka作为一个强大的消息代理系统,与Spring框架的集成使得在分布式应用中处理消息变得更加简单和灵活。Spring Kafka提供了 @KafkaListener 注解,为开发者提供了一种声明式的方式来定义消息监听器。在本文中,我们将深入探讨 @KafkaListener 注解的各种参数以及它们的使用方

    2024年01月16日
    浏览(51)
  • Spring-2-深入理解Spring 注解依赖注入(DI):简化Java应用程序开发

      掌握纯注解开发依赖注入(DI)模式 学习使用纯注解进行第三方Bean注入 问题导入 思考:如何使用注解方式将Bean对象注入到类中 1.1 使用@Autowired注解开启自动装配模式(按类型) 说明:不管是使用配置文件还是配置类,都必须进行对应的Spring注解包扫描才可以使用。@Autowired默

    2024年02月14日
    浏览(58)
  • Spring高手之路——深入理解注解驱动配置与XML配置的融合与区别

       XML 配置中,我们通常采用 ClassPathXmlApplicationContext ,它能够加载类路径下的 XML 配置文件来初始化 Spring 应用上下文。然而,在注解驱动的配置中,我们则使用以 Annotation 开头和 ApplicationContext 结尾的类,如 AnnotationConfigApplicationContext 。 AnnotationConfigApplicationContext 是 Spri

    2024年02月06日
    浏览(44)
  • springboot @Async 异步调用接口处理数据

    @Async 异步背景 新增的数据需要分发给下游业务系统,由于下游业务系统状态未知,所以需要异步发送数据给下游业务系统。 系统生效按钮---controller新增--异步调用servcie---数据集成 在springboot框架中实现步骤 首先在启动类上加上 @EnableAsync 注解开启项目的异步调用功能,其次

    2024年02月16日
    浏览(42)
  • Spring高手之路2——深入理解注解驱动配置与XML配置的融合与区别

       XML 配置中,我们通常采用 ClassPathXmlApplicationContext ,它能够加载类路径下的 XML 配置文件来初始化 Spring 应用上下文。然而,在注解驱动的配置中,我们则使用以 Annotation 开头和 ApplicationContext 结尾的类,如 AnnotationConfigApplicationContext 。 AnnotationConfigApplicationContext 是 Spri

    2024年02月08日
    浏览(52)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包