深入理解 @Async 和 CompletableFuture:异步编程的两种方式

1,124 阅读5分钟

文章结构

  1. 介绍
  2. 如何使用 @Async
  3. 如何使用 CompletableFuture
  4. @Async 与 CompletableFuture 的区别
  5. 应用场景对比
  6. 常见问题和解决方案

1. 介绍

在现代应用程序中,异步编程是一种提高系统性能、响应性和可扩展性的重要手段。Spring 框架提供了 @Async 注解来简化异步方法的执行,而 Java 8 的 CompletableFuture 则提供了一种更加灵活和功能丰富的方式来处理异步任务。本文将介绍这两种异步编程的方式,并对比它们的区别和应用场景。


2. 如何使用 @Async

@Async 注解是 Spring 提供的用于简化异步编程的一种方式。它允许我们将方法标记为异步执行,Spring 会在后台线程池中异步执行这些方法。

使用步骤:

  1. 启用异步支持: 在 Spring Boot 应用中,使用 @EnableAsync 注解启用异步方法支持。通常在主类中配置。

    @SpringBootApplication
    @EnableAsync
    public class MyApplication {
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
    
  2. 使用 @Async 注解异步方法: 在需要异步执行的业务方法上使用 @Async 注解。

    @Service
    public class MyService {
        @Async
        public CompletableFuture<String> asyncMethod() {
            try {
                Thread.sleep(2000); // 模拟长时间任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return CompletableFuture.completedFuture("Task Completed");
        }
    }
    
  3. 调用异步方法: 注入 MyService 并调用 asyncMethod(),Spring 会在后台线程池中异步执行它。

    @RestController
    public class MyController {
        @Autowired
        private MyService myService;
    
        @GetMapping("/start")
        public CompletableFuture<String> startTask() {
            return myService.asyncMethod();
        }
    }
    

配置线程池:

默认情况下,Spring 使用 SimpleAsyncTaskExecutor 作为异步方法的执行线程池,但在生产环境中,通常需要自定义线程池配置。

@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(500);
        executor.initialize();
        return executor;
    }
}

3. 如何使用 CompletableFuture

CompletableFuture 是 Java 8 引入的一个强大的异步计算类,它提供了比传统的 Future 更丰富的功能,能够支持链式调用、组合多个异步任务等操作。

使用步骤:

  1. 创建异步任务: 使用 CompletableFuture.supplyAsync() 启动一个异步任务。

    public class MyService {
        public CompletableFuture<String> asyncMethod() {
            return CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(2000); // 模拟长时间任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "Task Completed";
            });
        }
    }
    
  2. 组合异步任务CompletableFuture 支持任务链式调用,可以通过 thenApply, thenCombine 等方法进行异步任务的组合。

    public class MyService {
        public CompletableFuture<String> asyncMethod() {
            return CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "Task Completed";
            }).thenApply(result -> result + " and Combined");
        }
    }
    
  3. 处理异常CompletableFuture 支持异步任务的异常处理,可以使用 handleexceptionally 方法。

    CompletableFuture.supplyAsync(() -> {
        // 异常发生
        throw new RuntimeException("Something went wrong");
    }).exceptionally(ex -> "Handled Exception: " + ex.getMessage());
    
  4. 等待结果: 可以使用 get()join() 等方法来获取异步任务的结果。

    CompletableFuture<String> future = asyncMethod();
    String result = future.join();  // 阻塞直到任务完成
    

4. @Async 与 CompletableFuture 的区别

特性@AsyncCompletableFuture
支持链式调用不支持支持通过 thenApply(), thenCombine() 等方法
返回类型默认返回 voidFuture,需要自行处理返回 CompletableFuture 对象
异常处理异常处理需要手动实现支持内建的异常处理机制,如 exceptionally()
多任务组合不支持多任务组合支持通过 thenCombine() 等方法组合多个任务
线程池配置默认使用 SimpleAsyncTaskExecutor需要手动配置线程池
可读性简洁,注解驱动灵活,适合复杂的异步任务和任务组合
适用场景简单的异步任务,通常用于服务层、控制层复杂的异步任务组合,任务依赖、异步回调等场景

5. 应用场景对比

1. 使用 @Async 的场景:

  • 服务层的异步操作:在 Spring 服务层中,当某个方法的执行不会影响主流程时,可以使用 @Async 进行异步处理。
  • 轻量级异步任务:适用于不需要复杂组合的简单异步任务,如发送邮件、异步日志记录等。

2. 使用 CompletableFuture 的场景:

  • 任务组合:当多个异步任务之间有依赖关系时,CompletableFuture 可以让你轻松地将多个任务串联起来。
  • 复杂的异步操作:如果需要处理多个异步任务的结果,或者需要执行多个异步操作并发执行,CompletableFuture 提供了更强大的功能。
  • 错误处理:在需要复杂的错误处理机制时,CompletableFuture 提供了内建的异常处理功能,能够使代码更加简洁。

6. 常见问题和解决方案

问题 1: @Async 注解不起作用

  • 原因:没有启用 @EnableAsync 注解,或者异步方法没有返回 Future 类型。
  • 解决方案:确保在主类中启用了 @EnableAsync 注解,并确保异步方法返回 Future 或其子类(如 CompletableFuture)。

问题 2: CompletableFuture 无法获取结果

  • 原因:调用 get() 方法时线程未完成,导致阻塞。
  • 解决方案:使用 join() 方法来等待任务完成,或者使用 CompletableFuture 的回调方法,如 thenApply()

问题 3: 异常未捕获

  • 原因:异步任务执行过程中发生了异常,但没有正确处理。
  • 解决方案:使用 exceptionally()handle() 方法处理 CompletableFuture 中的异常。

总结

  • @Async 是一种简单、易用的异步处理方式,适用于轻量级的异步任务,能够与 Spring 的其他功能(如事务管理)无缝集成。
  • CompletableFuture 提供了更强大、更灵活的异步编程支持,适用于复杂的异步任务组合和错误处理。

根据项目的需求选择合适的异步处理方式,可以使应用程序更加高效和响应迅速。