07-CompletableFuture异步编程实战与陷阱规避

14 阅读4分钟

CompletableFuture异步编程实战与陷阱规避

一、为什么需要异步编程?

1. 同步 vs 异步性能对比

场景同步调用耗时异步调用耗时提升比例
3次顺序HTTP请求900ms300ms300%
数据库查询+文件IO450ms150ms300%
多服务聚合结果1200ms400ms300%

2. 回调地狱的演进

// 回调地狱示例
userService.getUser(id, user -> {
    orderService.getOrders(user, orders -> {
        paymentService.getPayments(orders, payments -> {
            // 嵌套层级越来越深...
        });
    });
});
​
// CompletableFuture解决方案
CompletableFuture.supplyAsync(() -> userService.getUser(id))
    .thenCompose(user -> orderService.getOrdersAsync(user))
    .thenAccept(orders -> paymentService.processPayments(orders));

二、核心API全解析

1. 创建异步任务

方法作用描述线程池控制
supplyAsync(Supplier)带返回值的异步任务可指定Executor
runAsync(Runnable)无返回值的异步任务默认ForkJoinPool.commonPool()
completedFuture(T)创建已完成的Future-
// 自定义线程池示例
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 耗时操作
    return "result";
}, executor);

2. 任务链式组合

组合方法作用描述异常处理特性
thenApply(Function)同步转换结果异常会中断整个链
thenCompose(Function)异步嵌套Future支持异常传播
thenCombine(CompletionStage, BiFunction)双任务结果合并任一任务异常则中断
// 组合操作示例
CompletableFuture<Integer> total = getUserCount()
    .thenCombine(getOrderCount(), (userCount, orderCount) -> {
        return userCount + orderCount;
    });

三、异常处理机制

1. 异常传播机制

graph LR
    A[任务A异常] --> B[中断后续thenApply]
    B --> C[触发handle/handler]
    C --> D[恢复默认值或新异常]

2. 处理方案对比

方法作用描述适用场景
exceptionally(Function)捕获异常并返回默认值简单降级逻辑
handle(BiFunction)同时处理正常结果和异常需要统一处理
whenComplete(BiConsumer)观察结果但不改变结果日志记录等副作用操作
// 异常处理示例
CompletableFuture.supplyAsync(() -> {
        if (new Random().nextBoolean()) {
            throw new RuntimeException("模拟异常");
        }
        return "success";
    })
    .exceptionally(ex -> {
        System.out.println("捕获异常: " + ex.getMessage());
        return "default";
    })
    .thenAccept(System.out::println);

四、高级组合模式

1. 多任务聚合

方法行为特点
allOf(CompletableFuture...)等待所有任务完成
anyOf(CompletableFuture...)任意一个任务完成即继续
// 电商商品详情页聚合示例
CompletableFuture<Product> productFuture = getProductAsync(id);
CompletableFuture<List<Review>> reviewsFuture = getReviewsAsync(id);
CompletableFuture<Recommendation> recFuture = getRecommendationsAsync(id);
​
CompletableFuture<Void> all = CompletableFuture.allOf(
    productFuture, reviewsFuture, recFuture);
​
all.thenRun(() -> {
    Product p = productFuture.join();
    List<Review> reviews = reviewsFuture.join();
    Recommendation rec = recFuture.join();
    renderPage(p, reviews, rec);
});

2. 超时控制实现

// JDK9+ 原生支持
future.orTimeout(1, TimeUnit.SECONDS)
      .exceptionally(ex -> "超时默认值");
​
// JDK8兼容方案
CompletableFuture.supplyAsync(() -> {
    try {
        return longRunningTask();
    } finally {
        timeoutFuture.cancel(true);
    }
}).acceptEither(timeoutFuture, result -> {
    // 处理正常结果
});

五、性能陷阱与最佳实践

1. 线程池选择策略

场景推荐线程池理由
IO密集型任务固定大小线程池(>CPU核数)避免等待IO时CPU闲置
CPU密集型任务工作窃取线程池利用多核并行计算
混合型任务自定义线程池根据业务特点调整

2. 常见陷阱清单

  • 阻塞线程池:默认ForkJoinPool的共用特性

    // 错误用法:会阻塞公共线程池
    CompletableFuture.runAsync(() -> {
        Thread.sleep(1000); // 阻塞调用
    });
    ​
    // 正确方案:使用自定义线程池
    ExecutorService ioExecutor = Executors.newCachedThreadPool();
    CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1); // 使用可中断休眠
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }, ioExecutor);
    
  • 丢失异常:未处理的异常会被静默吞噬

    future.exceptionally(ex -> {
        log.error("任务失败", ex); // 必须记录日志
        return null;
    });
    

六、Spring整合实战

1. @Async与CompletableFuture

@Service
public class OrderService {
    @Async("taskExecutor") // 指定线程池
    public CompletableFuture<Order> getOrderAsync(String id) {
        return CompletableFuture.completedFuture(queryOrder(id));
    }
}
​
// 调用方
orderService.getOrderAsync("123")
    .thenApply(Order::getItems)
    .thenAccept(this::processItems);

2. WebFlux响应式整合

@GetMapping("/user/{id}/profile")
public Mono<UserProfile> getUserProfile(@PathVariable String id) {
    return Mono.fromFuture(
        userService.getUserAsync(id)
            .thenCompose(user -> 
                statsService.getUserStatsAsync(user.getId())
                    .thenApply(stats -> new UserProfile(user, stats))
    );
}

七、QA高频问题

💬 Q1:如何处理多个异步任务的异常?

答案

  1. 为每个任务单独添加exceptionally处理

  2. 使用handle统一处理:

    CompletableFuture.allOf(task1, task2)
        .handle((result, ex) -> {
            if (ex != null) {
                // 检查具体哪个任务失败
                if (task1.isCompletedExceptionally()) {
                    // 处理task1异常
                }
                return "fallback";
            }
            return result;
        });
    

💬 Q2:为什么我的回调没有执行?

检查清单

  1. 主线程是否提前退出(添加future.join()
  2. 是否遗漏了终端操作(如thenAccept
  3. 线程池是否已被关闭

💬 Q3:与RxJava如何选择?

对比决策表

维度CompletableFutureRxJava
学习曲线低(JDK内置)高(响应式编程概念)
适用场景离散异步任务数据流处理
背压支持不支持原生支持
操作符丰富度基础组合操作丰富的流操作符

最佳实践总结

  1. 始终指定业务专属线程池(避免影响公共线程池)
  2. 为每个异步链添加异常处理
  3. 复杂场景优先使用thenCompose而非thenApply
  4. 监控线程池状态(如Spring Boot Actuator)

通过jstack -l <pid>可查看CompletableFuture实际使用的线程栈