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。

相关推荐
代码or搬砖2 小时前
Collections和Arrays
java·开发语言
Yiii_x2 小时前
Object类与包装类
java·经验分享·笔记·课程设计·ai编程
吴名氏.2 小时前
电子书《Java程序设计与应用开发(第3版)》
java·开发语言·java程序设计与应用开发
喵手2 小时前
数字处理的那些事:从 `Math` 到 `BigDecimal`,如何玩转数字与随机数?
java·数字处理
Wang15302 小时前
2025-2026 Java核心技术热点全景解析:从LTS革新到生态跃迁,筑牢后端技术核心竞争力
java
ss2732 小时前
ScheduledThreadPoolExecutor异常处理
java·开发语言
ssschema3 小时前
M4芯片MAC安装java环境
java·macos
星辰_mya3 小时前
RocketMQ
java·rocketmq·java-rocketmq
一叶飘零_sweeeet3 小时前
2025 实战复盘:物联网 + 数据检索融合项目的核心技术实现与心得
java·物联网·mqtt