随着硬件和软件的高度发展,现代应用变得更加复杂和要求更高。由于 高需求,工程师总是试图寻找新的方法来提高应用程序性能和响应能力。慢节奏应用程序的一种解决方案是实施异步方法。异步处理是一种执行任务并发运行的进程或函数,无需等待一个任务完成后再开始另一个任务。在本文中,我将尝试探索 Spring Boot 中的异步方法和 @Async 注解,试图解释多线程和并发之间的区别,以及何时使用或避免它。
Spring 中的 @Async 注解支持方法调用的异步处理。它指示框架在单独的线程中执行该方法,允许调用者继续执行而无需等待该方法完成。这 提高了应用程序的整体响应能力和吞吐量。
要使用@Async,您必须首先通过将@EnableAsync注释添加到配置类来在应用程序中启用异步处理:
@Configuration
@EnableAsync
public class AppConfig {
}
接下来,用@Async注解来注解你想要异步执行的方法:
@Service
public class AsyncService {
@Async
public void asyncMethod() {
// Perform time-consuming task
}
}
有时,区分多线程和并发与并行执行可能会让人感到困惑,但是,两者都与并行执行相关。他们每个人都有自己的用例和实现:
综上所述,@Async是一种更高层次的抽象,它为开发人员简化了异步处理,而多线程和并发更多的是手动管理并行执行。
使用异步方法似乎非常直观,但是,必须考虑到这种方法也有注意事项。
在以下情况下使用@Async:
在以下情况下避免使用 @Async:
在此示例中,我们将创建一个简单的 Spring Boot 应用程序来演示 @Async 的使用。 让我们创建一个简单的订单管理服务。
org.springframework.boot:spring-boot-starter
org.springframework.boot:spring-boot-starter-web
Web 依赖用于 REST 端点演示目的。 @Async 带有引导启动程序。
@SpringBootApplication
@EnableAsync
public class AsyncDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
}
@Configuration
@EnableAsync
public class ApplicationConfig {}
@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;
}
}
通过此配置,我们可以控制最大和默认线程池大小。以及其他有用的定制。
@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()
在结果可用之前会阻塞。如果我们想在结果可用时执行进一步的异步操作,我们可以使用thenApply
、thenAccept
或 CompletableFuture 提供的其他方法。
@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使用代理来添加异步行为,并且在内部调用方法会绕过代理。为了使其发挥作用,我们可以:
Spring 中的 @Async 注解是在应用程序中启用异步处理的强大工具。通过使用@Async,我们不需要陷入并发管理和多线程的复杂性来增强应用程序的响应能力和性能。但要决定何时使用 @Async 或使用替代并发 使用程序,了解其局限性和用例非常重要。