02.02.02 CompletableFuture 组合与异常处理:构建复杂异步流

02.02.02 CompletableFuture 组合与异常处理:构建复杂异步流

导读

在实际项目中,我们经常需要并行执行多个异步任务,然后合并它们的结果;或者需要处理复杂的异常场景,实现优雅降级。这正是 CompletableFuture 组合 API 大显身手的地方。

本文将深入探讨 CompletableFuture 的组合模式和异常处理机制,帮助你构建健壮的异步流程。

适用人群:已掌握 CompletableFuture 基础 API 的开发者

学习目标

  • 掌握 thenCombineallOfanyOf 等组合方法
  • 理解异常传播机制和处理策略
  • 能够设计容错的异步流程

一、任务组合模式

1.1 thenCombine:合并两个独立任务

当两个异步任务相互独立,需要等待两者都完成后合并结果时,使用 thenCombine

java 复制代码
// 场景:并行获取用户信息和订单信息,然后合并
CompletableFuture<User> userFuture = CompletableFuture
    .supplyAsync(() -> userService.getUser(userId), ioExecutor);

CompletableFuture<List<Order>> ordersFuture = CompletableFuture
    .supplyAsync(() -> orderService.getOrders(userId), ioExecutor);

// 两个任务并行执行,完成后合并
CompletableFuture<UserProfile> profileFuture = userFuture.thenCombine(
    ordersFuture,
    (user, orders) -> new UserProfile(user, orders)
);

// 性能对比
// 串行:time = time(user) + time(orders) = 2s
// 并行:time = max(time(user), time(orders)) = 1s

三种变体

java 复制代码
// thenCombine:合并两个结果 (T, U) -> V
CompletableFuture<V> combined = cf1.thenCombine(cf2, (t, u) -> combine(t, u));

// thenAcceptBoth:消费两个结果 (T, U) -> void
CompletableFuture<Void> consumed = cf1.thenAcceptBoth(cf2, (t, u) -> process(t, u));

// runAfterBoth:两个都完成后执行 () -> void
CompletableFuture<Void> after = cf1.runAfterBoth(cf2, () -> cleanup());

1.2 allOf:等待所有任务完成

当需要等待多个任务全部完成时,使用 allOf

java 复制代码
// 场景:并行调用多个服务,等待全部完成
CompletableFuture<String> task1 = fetchFromService1();
CompletableFuture<String> task2 = fetchFromService2();
CompletableFuture<String> task3 = fetchFromService3();

// allOf 返回 CompletableFuture<Void>,只关心完成状态
CompletableFuture<Void> allDone = CompletableFuture.allOf(task1, task2, task3);

// 收集结果(需要手动处理)
CompletableFuture<List<String>> results = allDone.thenApply(v -> {
    List<String> list = new ArrayList<>();
    list.add(task1.join());  // 已完成,join() 不会阻塞
    list.add(task2.join());
    list.add(task3.join());
    return list;
});

通用工具方法

java 复制代码
// 封装:将多个 CompletableFuture 的结果收集为 List
public static <T> CompletableFuture<List<T>> allOfList(
        List<CompletableFuture<T>> futures) {
    
    CompletableFuture<Void> allDone = CompletableFuture.allOf(
        futures.toArray(new CompletableFuture[0])
    );
    
    return allDone.thenApply(v -> 
        futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList())
    );
}

// 使用
List<CompletableFuture<User>> userFutures = userIds.stream()
    .map(id -> getUserAsync(id))
    .collect(Collectors.toList());

CompletableFuture<List<User>> allUsers = allOfList(userFutures);

带异常处理的收集

java 复制代码
// 即使部分任务失败,也收集成功的结果
public static <T> CompletableFuture<List<T>> allOfWithFallback(
        List<CompletableFuture<T>> futures, T fallback) {
    
    // 为每个任务添加异常处理
    List<CompletableFuture<T>> safeFutures = futures.stream()
        .map(f -> f.exceptionally(ex -> {
            log.warn("任务失败,使用默认值", ex);
            return fallback;
        }))
        .collect(Collectors.toList());
    
    return allOfList(safeFutures);
}

1.3 anyOf:任一任务完成

当只需要最快完成的结果时,使用 anyOf

java 复制代码
// 场景:从多个数据源获取,使用最快的结果
CompletableFuture<String> source1 = fetchFromCache();   // 快
CompletableFuture<String> source2 = fetchFromDB();      // 中
CompletableFuture<String> source3 = fetchFromRemote();  // 慢

// anyOf 返回 Object,需要类型转换
CompletableFuture<Object> fastest = CompletableFuture.anyOf(
    source1, source2, source3
);

String result = (String) fastest.join();

// 取消其他任务(节省资源)
source1.cancel(true);
source2.cancel(true);
source3.cancel(true);

超时控制模式

java 复制代码
// 使用 anyOf 实现超时
CompletableFuture<String> task = fetchData();
CompletableFuture<String> timeout = CompletableFuture
    .supplyAsync(() -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        throw new TimeoutException("超时");
    });

CompletableFuture<Object> result = CompletableFuture.anyOf(task, timeout);

1.4 Either 模式:任一完成即处理

java 复制代码
// applyToEither:任一完成后转换
CompletableFuture<String> result = source1.applyToEither(
    source2,
    data -> process(data)  // 处理先完成的结果
);

// acceptEither:任一完成后消费
CompletableFuture<Void> consumed = source1.acceptEither(
    source2,
    data -> System.out.println("收到: " + data)
);

// runAfterEither:任一完成后执行
CompletableFuture<Void> after = source1.runAfterEither(
    source2,
    () -> log.info("有一个任务完成了")
);

二、异常处理机制

2.1 异常传播规则

CompletableFuture 中的异常会沿着链向下传播,直到被处理:

java 复制代码
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> {
        throw new RuntimeException("任务失败");
    })
    .thenApply(s -> s.toUpperCase())    // ❌ 不会执行
    .thenApply(s -> s + " processed");  // ❌ 不会执行

// 最终调用 get()/join() 时抛出 CompletionException
try {
    future.join();
} catch (CompletionException e) {
    System.out.println("捕获异常: " + e.getCause().getMessage());
}

异常包装规则

java 复制代码
// CompletableFuture 内部异常会被包装
// 原始异常 -> CompletionException(原始异常)

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    throw new IllegalArgumentException("参数错误");
});

try {
    future.join();
} catch (CompletionException e) {
    Throwable cause = e.getCause();  // IllegalArgumentException
    System.out.println(cause.getClass().getName());  // IllegalArgumentException
}

2.2 exceptionally:捕获并恢复

java 复制代码
// 捕获异常,返回默认值
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> {
        if (Math.random() > 0.5) {
            throw new RuntimeException("失败");
        }
        return "成功";
    })
    .exceptionally(ex -> {
        log.error("任务失败", ex);
        return "默认值";  // 恢复为正常值
    });

// 链式异常处理
CompletableFuture<String> result = fetchData()
    .exceptionally(ex -> {
        if (ex instanceof NetworkException) {
            return tryFromCache();  // 网络异常,尝试缓存
        }
        throw new RuntimeException(ex);  // 其他异常继续传播
    })
    .exceptionally(ex -> {
        return "最终默认值";  // 最后的兜底
    });

2.3 handle:统一处理成功与失败

java 复制代码
// handle 同时处理成功和失败,可以改变结果
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> riskyOperation())
    .handle((result, ex) -> {
        if (ex != null) {
            // 处理异常
            log.error("操作失败", ex);
            return "错误: " + ex.getMessage();
        }
        // 处理成功
        return "成功: " + result;
    });

// 对比 whenComplete 和 handle
// whenComplete:执行副作用,不改变结果,异常继续传播
// handle:可以改变结果,可以恢复异常

2.4 异常分类处理

java 复制代码
// 根据异常类型采取不同策略
CompletableFuture<String> result = fetchData()
    .exceptionally(ex -> {
        Throwable cause = ex.getCause() != null ? ex.getCause() : ex;
        
        if (cause instanceof TimeoutException) {
            log.warn("请求超时,使用缓存");
            return cache.get(key);
        }
        
        if (cause instanceof ValidationException) {
            log.error("参数校验失败");
            throw new BusinessException("参数错误", cause);
        }
        
        if (cause instanceof NetworkException) {
            log.warn("网络异常,重试");
            return retryFetch();
        }
        
        // 未知异常
        log.error("未知错误", cause);
        throw new SystemException("系统错误", cause);
    });

2.5 多层异常处理

java 复制代码
// 设计多层异常处理策略
CompletableFuture<OrderResult> result = CompletableFuture
    .supplyAsync(() -> orderService.createOrder(request))
    
    // 第一层:业务异常处理
    .exceptionally(ex -> {
        if (ex.getCause() instanceof InsufficientStockException) {
            return OrderResult.outOfStock();
        }
        if (ex.getCause() instanceof PaymentFailedException) {
            return OrderResult.paymentFailed();
        }
        throw new RuntimeException(ex);  // 其他异常继续传播
    })
    
    // 第二层:日志记录
    .whenComplete((r, ex) -> {
        if (ex != null) {
            log.error("订单创建失败", ex);
            metrics.increment("order.failure");
        } else {
            log.info("订单创建成功: {}", r.getOrderId());
            metrics.increment("order.success");
        }
    })
    
    // 第三层:最终兜底
    .handle((r, ex) -> {
        if (ex != null) {
            return OrderResult.systemError(ex.getMessage());
        }
        return r;
    });

三、超时控制(JDK 9+)

3.1 orTimeout:超时抛异常

java 复制代码
// 超时后抛出 TimeoutException
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> slowOperation())
    .orTimeout(2, TimeUnit.SECONDS)  // 2 秒超时
    .exceptionally(ex -> {
        if (ex instanceof TimeoutException) {
            return "超时,使用默认值";
        }
        throw new RuntimeException(ex);
    });

3.2 completeOnTimeout:超时使用默认值

java 复制代码
// 超时后使用默认值,不抛异常
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> slowOperation())
    .completeOnTimeout("默认值", 2, TimeUnit.SECONDS);

3.3 JDK 8 兼容方案

java 复制代码
// JDK 8 实现超时控制
public <T> CompletableFuture<T> withTimeout(
        CompletableFuture<T> future, long timeout, TimeUnit unit) {
    
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    
    CompletableFuture<T> result = new CompletableFuture<>();
    
    // 设置超时任务
    ScheduledFuture<?> timeoutTask = scheduler.schedule(() -> {
        if (!result.isDone()) {
            result.completeExceptionally(
                new TimeoutException("操作超时: " + timeout + " " + unit)
            );
        }
    }, timeout, unit);
    
    // 原任务完成时,取消超时任务
    future.whenComplete((value, ex) -> {
        timeoutTask.cancel(false);
        if (ex != null) {
            result.completeExceptionally(ex);
        } else {
            result.complete(value);
        }
    });
    
    return result;
}

// 使用
CompletableFuture<String> future = withTimeout(
    fetchData(), 2, TimeUnit.SECONDS
);

四、组合模式实战

4.1 API 聚合网关

java 复制代码
public CompletableFuture<Dashboard> getDashboard(String userId) {
    // 并行获取三个数据源
    CompletableFuture<Profile> profileFuture = CompletableFuture
        .supplyAsync(() -> userApi.getProfile(userId), ioExecutor);
    
    CompletableFuture<List<Order>> ordersFuture = CompletableFuture
        .supplyAsync(() -> orderApi.getRecentOrders(userId), ioExecutor);
    
    CompletableFuture<List<Coupon>> couponsFuture = CompletableFuture
        .supplyAsync(() -> couponApi.getAvailableCoupons(userId), ioExecutor);
    
    // 等待所有完成,设置超时
    return CompletableFuture.allOf(profileFuture, ordersFuture, couponsFuture)
        .orTimeout(2, TimeUnit.SECONDS)
        .thenApply(v -> new Dashboard(
            profileFuture.join(),
            ordersFuture.join(),
            couponsFuture.join()
        ))
        .exceptionally(ex -> {
            log.error("聚合失败", ex);
            return Dashboard.fallback(userId);
        });
}

4.2 带降级的聚合

java 复制代码
public CompletableFuture<Dashboard> getDashboardWithFallback(String userId) {
    // 每个任务独立处理异常,不影响其他任务
    CompletableFuture<Profile> profileFuture = CompletableFuture
        .supplyAsync(() -> userApi.getProfile(userId), ioExecutor)
        .orTimeout(1, TimeUnit.SECONDS)
        .exceptionally(ex -> {
            log.warn("获取用户信息失败", ex);
            return Profile.anonymous();
        });
    
    CompletableFuture<List<Order>> ordersFuture = CompletableFuture
        .supplyAsync(() -> orderApi.getRecentOrders(userId), ioExecutor)
        .orTimeout(1, TimeUnit.SECONDS)
        .exceptionally(ex -> {
            log.warn("获取订单失败", ex);
            return Collections.emptyList();
        });
    
    CompletableFuture<List<Coupon>> couponsFuture = CompletableFuture
        .supplyAsync(() -> couponApi.getAvailableCoupons(userId), ioExecutor)
        .orTimeout(1, TimeUnit.SECONDS)
        .exceptionally(ex -> {
            log.warn("获取优惠券失败", ex);
            return Collections.emptyList();
        });
    
    // 即使部分失败,也能返回有效结果
    return CompletableFuture.allOf(profileFuture, ordersFuture, couponsFuture)
        .thenApply(v -> new Dashboard(
            profileFuture.join(),
            ordersFuture.join(),
            couponsFuture.join()
        ));
}

4.3 级联异步调用

java 复制代码
public CompletableFuture<String> cascadingCall(String userId) {
    // 第一级:获取用户(超时 1 秒)
    CompletableFuture<User> userFuture = CompletableFuture
        .supplyAsync(() -> userService.getUser(userId), ioExecutor)
        .orTimeout(1, TimeUnit.SECONDS)
        .exceptionally(ex -> User.defaultUser());
    
    // 第二级:获取订单(依赖用户,超时 2 秒)
    CompletableFuture<List<Order>> ordersFuture = userFuture
        .thenCompose(user -> CompletableFuture
            .supplyAsync(() -> orderService.getOrders(user.getId()), ioExecutor)
            .orTimeout(2, TimeUnit.SECONDS)
            .exceptionally(ex -> Collections.emptyList())
        );
    
    // 第三级:获取优惠券(与订单并行,超时 1 秒)
    CompletableFuture<List<Coupon>> couponsFuture = userFuture
        .thenCompose(user -> CompletableFuture
            .supplyAsync(() -> couponService.getCoupons(user.getId()), ioExecutor)
            .orTimeout(1, TimeUnit.SECONDS)
            .exceptionally(ex -> Collections.emptyList())
        );
    
    // 合并结果
    return ordersFuture.thenCombine(couponsFuture, (orders, coupons) -> {
        return buildResponse(orders, coupons);
    });
}

五、组合策略对比

方法 输入 返回 用途
thenCombine 2 个 CF CF<V> 合并两个结果
thenAcceptBoth 2 个 CF CF<Void> 消费两个结果
allOf N 个 CF CF<Void> 等待所有完成
anyOf N 个 CF CF<Object> 任一完成
applyToEither 2 个 CF CF<U> 任一完成后转换
acceptEither 2 个 CF CF<Void> 任一完成后消费

六、异常处理对比

方法 输入 改变结果 异常传播
exceptionally Throwable -> T ✅ 可以恢复 ❌ 阻断
whenComplete (T, Throwable) -> void ❌ 不改变 ✅ 继续
handle (T, Throwable) -> U ✅ 可以转换 ❌ 阻断

七、最佳实践

7.1 设计原则

  1. 快速失败 vs 优雅降级:根据业务需求选择
  2. 独立异常处理:每个任务独立处理异常,避免连锁失败
  3. 超时控制:所有远程调用必须设置超时
  4. 日志追踪 :使用 whenComplete 记录日志,不影响结果

7.2 常见陷阱

java 复制代码
// ❌ 陷阱1:忘记处理异常
CompletableFuture.supplyAsync(() -> riskyOperation())
    .thenApply(result -> process(result));
// 异常会被吞掉!

// ✅ 正确:添加异常处理
CompletableFuture.supplyAsync(() -> riskyOperation())
    .thenApply(result -> process(result))
    .exceptionally(ex -> {
        log.error("处理失败", ex);
        return defaultValue;
    });

// ❌ 陷阱2:exceptionally 中再次抛异常
.exceptionally(ex -> {
    throw new RuntimeException(ex);  // 新异常继续传播
});

// ❌ 陷阱3:allOf 不返回结果
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);
// all.join() 只是 void,需要手动从 f1, f2 获取结果

八、总结

CompletableFuture 的组合 API 让我们能够构建复杂的异步流程:

  1. 组合模式thenCombine 合并、allOf 等待全部、anyOf 取最快
  2. 异常处理exceptionally 恢复、handle 统一处理、whenComplete 副作用
  3. 超时控制orTimeout(JDK 9+)或自定义实现

核心原则:每个异步任务独立处理异常,设置合理超时,提供降级策略。


上一篇回顾《CompletableFuture API 入门:告别阻塞式 Future》

我们从零开始掌握 CompletableFuture 的核心 APICompletableFuture。
下一篇预告《CompletableFuture 实战案例:电商订单与 API 聚合》

我们将通过完整的业务案例,展示如何在真实项目中应用 CompletableFuture。

相关推荐
言慢行善1 天前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星1 天前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟1 天前
操作系统之虚拟内存
java·服务器·网络
Tong Z1 天前
常见的限流算法和实现原理
java·开发语言
凭君语未可1 天前
Java 中的实现类是什么
java·开发语言
He少年1 天前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新1 天前
myeclipse的pojie
java·ide·myeclipse
迷藏4941 天前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏4941 天前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
qq_433502181 天前
Codex cli 飞书文档创建进阶实用命令 + Skill 创建&使用 小白完整教程
java·前端·飞书