什么是Spring Boot中的@Async

2024-02-02 14:45:42 浏览数 (1)

异步方法

随着硬件和软件的高度发展,现代应用变得更加复杂和要求更高。由于 高需求,工程师总是试图寻找新的方法来提高应用程序性能和响应能力。慢节奏应用程序的一种解决方案是实施异步方法。异步处理是一种执行任务并发运行的进程或函数,无需等待一个任务完成后再开始另一个任务。在本文中,我将尝试探索 Spring Boot 中的异步方法和 @Async 注解,试图解释多线程和并发之间的区别,以及何时使用或避免它。

Spring中的@Async是什么?

Spring 中的 @Async 注解支持方法调用的异步处理。它指示框架在单独的线程中执行该方法,允许调用者继续执行而无需等待该方法完成。这 提高了应用程序的整体响应能力和吞吐量。

要使用@Async,您必须首先通过将@EnableAsync注释添加到配置类来在应用程序中启用异步处理:

代码语言:javascript复制
@Configuration
@EnableAsync
public class AppConfig {
}

接下来,用@Async注解来注解你想要异步执行的方法:

代码语言:javascript复制
@Service
public class AsyncService {
    @Async
    public void asyncMethod() {
        // Perform time-consuming task
    }
}

@Async 与多线程和并发有何不同?

有时,区分多线程和并发与并行执行可能会让人感到困惑,但是,两者都与并行执行相关。他们每个人都有自己的用例和实现:

  • @Async 注解是 Spring 框架特定的抽象,它支持异步执行。它提供了轻松使用异步的能力,在后台处理所有艰苦的工作,例如线程创建、管理和执行。这使用户能够专注于业务逻辑而不是底层细节。
  • 多线程是一个通用概念,通常指操作系统或程序同时管理多个线程的能力。由于 @Async 帮助我们自动完成所有艰苦的工作,在这种情况下,我们可以手动处理所有这些工作并创建一个多线程环境。 Java 具有ThreadExecutorService等必要的类来创建和使用多线程。
  • 并发是一个更广泛的概念,它涵盖多线程和并行执行技术。它是 系统在一个或多个处理器上同时执行多个任务的能力。

综上所述,@Async是一种更高层次的抽象,它为开发人员简化了异步处理,而多线程和并发更多的是手动管理并行执行。

何时使用 @Async 以及何时避免它。

使用异步方法似乎非常直观,但是,必须考虑到这种方法也有注意事项。

在以下情况下使用@Async:

  • 您拥有可以并发运行的独立且耗时的任务,而不会影响应用程序的响应能力。
  • 您需要一种简单而干净的方法来启用异步处理,而无需深入研究低级线程管理。

在以下情况下避免使用 @Async:

  • 您想要异步执行的任务具有复杂的依赖性或需要大量的协调。在这种情况下,您可能需要使用更高级的并发 API,例如CompletableFuture或反应式编程库,例如 Project Reactor。
  • 您必须精确控制线程的管理方式,例如自定义线程池或高级同步机制。在这些情况下,请考虑使用 Java 的ExecutorService或其他并发实用程序。

在 Spring Boot 应用程序中使用 @Async。

在此示例中,我们将创建一个简单的 Spring Boot 应用程序来演示 @Async 的使用。 让我们创建一个简单的订单管理服务。

  1. 创建一个具有最低依赖要求的新 Spring Boot 项目: org.springframework.boot:spring-boot-starter org.springframework.boot:spring-boot-starter-web Web 依赖用于 REST 端点演示目的。 @Async 带有引导启动程序。
  2. 将 @EnableAsync 注释添加到主类或应用程序配置类(如果我们使用它):
代码语言:javascript复制
@SpringBootApplication
@EnableAsync
public class AsyncDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncDemoApplication.class, args);
  }
}
代码语言:javascript复制
@Configuration
@EnableAsync
public class ApplicationConfig {}
  1. 对于最佳解决方案,我们可以做的是,创建一个自定义 Executor bean 并根据我们的需要在同一个 Configuration 类中对其进行自定义:
代码语言:javascript复制
   @Configuration
   @EnableAsync
   public class ApplicationConfig {

     @Bean
     public Executor getAsyncExecutor() {
       ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
       executor.setCorePoolSize(5);
       executor.setMaxPoolSize(10);
       executor.setQueueCapacity(100);
       executor.setThreadNamePrefix("AsyncThread-");
       executor.initialize();
       return executor;
       }
   }

通过此配置,我们可以控制最大和默认线程池大小。以及其他有用的定制。

  1. 使用 @Async 方法创建 OrderService 类:
代码语言:javascript复制
@Service
public class OrderService {

  @Async
  public void saveOrderDetails(Order order) throws InterruptedException {
    Thread.sleep(2000);
    System.out.println(order.name());
  }

  @Async
  public CompletableFuture<String> saveOrderDetailsFuture(Order order) throws InterruptedException {
    System.out.println("Execute method with return type   "   Thread.currentThread().getName());
    String result = "Hello From CompletableFuture. Order: ".concat(order.name());
    Thread.sleep(5000);
    return CompletableFuture.completedFuture(result);
  }

  @Async
  public CompletableFuture<String> compute(Order order) throws InterruptedException {
    String result = "Hello From CompletableFuture CHAIN. Order: ".concat(order.name());
    Thread.sleep(5000);
    return CompletableFuture.completedFuture(result);
  }
}

我们在这里所做的是创建 3 种不同的异步方法。第一个saveOrderDetails服务是一个简单的异步 服务,它将开始异步计算。如果我们想使用现代异步Java功能, 例如CompletableFuture,我们可以通过服务来实现saveOrderDetailsFuture。通过这个服务,我们可以调用一个线程来等待@Async的结果。应该注意的是,CompletableFuture.get()在结果可用之前会阻塞。如果我们想在结果可用时执行进一步的异步操作,我们可以使用thenApplythenAccept或 CompletableFuture 提供的其他方法。

  1. 创建一个 REST 控制器来触发异步方法:
代码语言:javascript复制
@RestController
public class AsyncController {

 private final OrderService orderService;

  public OrderController(OrderService orderService) {
    this.orderService = orderService;
  }

  @PostMapping("/process")
  public ResponseEntity<Void> process(@RequestBody Order order) throws InterruptedException {
    System.out.println("PROCESSING STARTED");
    orderService.saveOrderDetails(order);
    return ResponseEntity.ok(null);
  }

  @PostMapping("/process/future")
  public ResponseEntity<String> processFuture(@RequestBody Order order) throws InterruptedException, ExecutionException {
    System.out.println("PROCESSING STARTED");
    CompletableFuture<String> orderDetailsFuture = orderService.saveOrderDetailsFuture(order);
    return ResponseEntity.ok(orderDetailsFuture.get());
  }

  @PostMapping("/process/future/chain")
  public ResponseEntity<Void> processFutureChain(@RequestBody Order order) throws InterruptedException, ExecutionException {
    System.out.println("PROCESSING STARTED");
    CompletableFuture<String> computeResult = orderService.compute(order);
    computeResult.thenApply(result -> result).thenAccept(System.out::println);
    return ResponseEntity.ok(null);
  }
}

现在,当我们访问/process端点时,服务器将立即返回响应,同时 继续saveOrderDetails()在后台执行。 2秒后,服务完成。第二个端点 -/process/future将使用我们的第二个选项,CompletableFuture在这种情况下,5 秒后,服务将完成,并将结果存储在CompletableFuture我们可以进一步使用future.get()来访问结果。在最后一个端点 - 中/process/future/chain,我们优化并使用了异步计算。控制器使用相同的服务方法CompletableFuture,但不久之后,我们将使用thenApply,thenAccept方法。服务器立即返回响应,我们不需要等待5秒,计算将在后台完成。在这种情况下,最重要的一点是对异步服务的调用,在我们的例子中compute()必须从同一类的外部完成。如果我们在一个方法上使用@Async并在同一个类中调用它,它将不起作用。这是因为Spring使用代理来添加异步行为,并且在内部调用方法会绕过代理。为了使其发挥作用,我们可以:

  • 将 @Async 方法移至单独的服务或组件。
  • 使用 ApplicationContext 获取代理并调用其上的方法。

总结

Spring 中的 @Async 注解是在应用程序中启用异步处理的强大工具。通过使用@Async,我们不需要陷入并发管理和多线程的复杂性来增强应用程序的响应能力和性能。但要决定何时使用 @Async 或使用替代并发 使用程序,了解其局限性和用例非常重要。

0 人点赞