CompletableFuture 链式编排:异步编程的优雅解法

CompletableFuture 链式编排:异步编程的优雅解法

Future 太简陋,Callback 太混乱,Reactor 太陡峭。CompletableFuture 是 Java 异步编程的优雅解法。本文拆解它的链式编排和组合操作。

一、为什么需要 CompletableFuture?

java 复制代码
// 1. Future 的局限: 只能 get(),不能链式操作
Future<String> future = executor.submit(() -> fetchData());
String result = future.get(); // ❌ 阻塞等待!
// 无法在完成后自动执行下一步操作

// 2. Callback 地狱: 回调嵌套
executor.submit(() -> {
    String data = fetchData();
    executor.submit(() -> {
        String processed = processData(data);
        executor.submit(() -> {
            String saved = saveData(processed);
            // 继续嵌套...
        });
    });
});

// 3. CompletableFuture: 链式编排
CompletableFuture.supplyAsync(() -> fetchData())
    .thenApply(this::processData)
    .thenAccept(this::saveData)
    .exceptionally(ex -> { log.error("失败", ex); return null; });
// 优雅!

二、CompletableFuture 基础

java 复制代码
// 创建 CompletableFuture
// 1. 无参: 已完成的
CompletableFuture<String> completed = CompletableFuture.completedFuture("hello");

// 2. supplyAsync: 异步执行有返回值的任务
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
    return fetchData();
});

// 3. runAsync: 异步执行无返回值的任务
CompletableFuture<Void> f2 = CompletableFuture.runAsync(() -> {
    doSomething();
});

// 4. 指定线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> fetchData(), pool);
css 复制代码
CompletableFuture 创建方式:

  ┌─────────────────────────────────────────────────────┐
  │  completedFuture(value)     → 已完成的 Future          │
  │  supplyAsync(supplier)     → 异步执行, 有返回值         │
  │  runAsync(runnable)        → 异步执行, 无返回值         │
  │  supplyAsync(supplier, pool) → 指定线程池              │
  │  new CompletableFuture()  → 手动创建 (可手动 complete)   │
  └─────────────────────────────────────────────────────┘

手动 complete

java 复制代码
// 手动控制 CompletableFuture 的完成
CompletableFuture<String> future = new CompletableFuture<>();

// 在另一个线程中完成
new Thread(() -> {
    Thread.sleep(1000);
    future.complete("done!"); // 手动完成
}).start();

String result = future.get(); // "done!"

三、链式操作

java 复制代码
// thenApply: 转换结果 (类似 map)
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> f2 = f1.thenApply(s -> s + " world");
// "hello world"

// thenAccept: 消费结果 (无返回值)
CompletableFuture<Void> f3 = f1.thenAccept(s -> System.out.println(s));
// 打印 "hello"

// thenRun: 不关心结果,执行后续操作
CompletableFuture<Void> f4 = f1.thenRun(() -> System.out.println("done"));

// thenCompose: 组合两个依赖的 Future (类似 flatMap)
CompletableFuture<String> f5 = CompletableFuture
    .supplyAsync(() -> "userId:123")
    .thenCompose(id -> CompletableFuture.supplyAsync(() -> fetchUser(id)));
// 先获取 ID,再用 ID 获取用户
css 复制代码
链式操作分类:

  ┌────────────────────────────────────────────────────────┐
  │  同步操作 (在当前线程执行)                                │
  │  ├── thenApply(fn)    转换结果 → CompletableFuture<T>   │
  │  ├── thenAccept(con)  消费结果 → CompletableFuture<Void> │
  │  └── thenRun(run)     忽略结果 → CompletableFuture<Void>  │
  │                                                        │
  │  异步操作 (在 ForkJoinPool 或指定线程池执行)               │
  │  ├── thenApplyAsync(fn)                                │
  │  ├── thenAcceptAsync(con)                              │
  │  └── thenRunAsync(run)                                 │
  │                                                        │
  │  组合操作                                               │
  │  ├── thenCompose(fn)  串联依赖 → flatMap                  │
  │  ├── thenCombine(other, fn) 合并两个独立结果               │
  │  └── applyToEither(other, fn) 取先完成的结果              │
  │                                                        │
  │  异常处理                                               │
  │  ├── exceptionally(fn)  捕获异常 → 恢复                   │
  │  ├── handle(fn)        同时处理正常和异常                 │
  │  └── whenComplete(fn)  完成后执行(不改变结果)              │
  └────────────────────────────────────────────────────────┘

thenApply vs thenCompose

java 复制代码
// thenApply: 转换 (map)
// fn 返回普通值
CompletableFuture<Integer> f = CompletableFuture
    .supplyAsync(() -> "123")
    .thenApply(s -> Integer.parseInt(s));
// CompletableFuture<Integer>

// thenCompose: 组合 (flatMap)
// fn 返回另一个 CompletableFuture
CompletableFuture<User> f2 = CompletableFuture
    .supplyAsync(() -> "userId:123")
    .thenCompose(id -> CompletableFuture.supplyAsync(() -> fetchUser(id)));
// CompletableFuture<User> (不是 CompletableFuture<CompletableFuture<User>>)

// 区别:
// thenApply:  T → R          → CompletableFuture<R>
// thenCompose: T → CompletableFuture<R> → CompletableFuture<R> (扁平化)

四、组合多个 Future

1. thenCombine: 合并两个独立结果

java 复制代码
// 合并两个独立的 Future
CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> "Tom");
CompletableFuture<Integer> ageFuture = CompletableFuture.supplyAsync(() -> 20);

CompletableFuture<String> result = nameFuture.thenCombine(ageFuture, (name, age) -> {
    return name + " is " + age + " years old";
});
// "Tom is 20 years old"

2. allOf: 等待全部完成

java 复制代码
// 等待所有 Future 完成
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> fetchUser());
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> fetchOrder());
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> fetchProduct());

CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.join(); // 等待全部完成

// allOf 返回 CompletableFuture<Void>,需要手动取结果
String user = f1.join();
String order = f2.join();
String product = f3.join();
java 复制代码
// 实战: 并行获取多个数据源
public Map<String, Object> fetchAll(Long userId) {
    CompletableFuture<User> userFuture = CompletableFuture
        .supplyAsync(() -> userService.findById(userId));
    CompletableFuture<List<Order>> orderFuture = CompletableFuture
        .supplyAsync(() -> orderService.findByUserId(userId));
    CompletableFuture<Account> accountFuture = CompletableFuture
        .supplyAsync(() -> accountService.findByUserId(userId));

    CompletableFuture.allOf(userFuture, orderFuture, accountFuture).join();

    return Map.of(
        "user", userFuture.join(),
        "orders", orderFuture.join(),
        "account", accountFuture.join()
    );
}
// 三个请求并行 → 总耗时 = max(3个请求耗时)
// 串行执行 → 总耗时 = 3个请求耗时之和

3. anyOf: 任一完成即返回

java 复制代码
// 等待任意一个完成
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(1000);
    return "slow";
});
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(100);
    return "fast";
});

CompletableFuture<Object> first = CompletableFuture.anyOf(f1, f2);
String result = (String) first.join(); // "fast" (先完成的)

4. applyToEither: 二选一

java 复制代码
// 从两个 Future 中取先完成的结果并转换
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> fetchFromPrimary());
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> fetchFromSecondary());

CompletableFuture<String> result = f1.applyToEither(f2, data -> process(data));
// 主备容灾: 先返回的数据先处理

五、异常处理

java 复制代码
// 1. exceptionally: 捕获异常并恢复
CompletableFuture<String> f = CompletableFuture
    .supplyAsync(() -> {
        if (error) throw new RuntimeException("fetch failed");
        return "data";
    })
    .exceptionally(ex -> {
        log.error("发生异常: {}", ex.getMessage());
        return "default"; // 返回默认值
    });

// 2. handle: 同时处理正常和异常
CompletableFuture<String> f2 = CompletableFuture
    .supplyAsync(() -> "data")
    .handle((result, ex) -> {
        if (ex != null) {
            log.error("异常", ex);
            return "fallback";
        }
        return result.toUpperCase();
    });

// 3. whenComplete: 不改变结果,仅执行副作用
CompletableFuture<String> f3 = CompletableFuture
    .supplyAsync(() -> "data")
    .whenComplete((result, ex) -> {
        if (ex != null) {
            log.error("失败", ex);
        } else {
            log.info("成功: {}", result);
        }
    });
// whenComplete 不改变结果,后续 still 获取原始结果
css 复制代码
异常处理区别:

  exceptionally(ex → fallback):
    └── 仅处理异常,正常结果不变
    └── 返回 fallback 替代异常

  handle((result, ex) → newResult):
    └── 同时处理正常和异常
    └── 可以改变正常和异常的结果

  whenComplete((result, ex) → void):
    └── 仅执行副作用 (日志/监控)
    └── 不改变结果 (异常仍会传递)

六、超时控制(JDK 9+)

java 复制代码
// JDK 9+: orTimeout 和 completeOnTimeout
CompletableFuture<String> f = CompletableFuture
    .supplyAsync(() -> fetchData())
    .orTimeout(3, TimeUnit.SECONDS); // 3秒超时 → 抛 TimeoutException

// 超时返回默认值
CompletableFuture<String> f2 = CompletableFuture
    .supplyAsync(() -> fetchData())
    .completeOnTimeout("default", 3, TimeUnit.SECONDS);
// 3秒超时 → 返回 "default"
java 复制代码
// JDK 8 手动超时控制
public <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout) {
    CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    scheduler.schedule(() -> timeoutFuture.complete(null), timeout, TimeUnit.MILLISECONDS);
    return future.applyToEither(timeoutFuture, Function.identity());
}

七、实际应用场景

1. 并行接口调用

java 复制代码
// 一个 API 需要聚合多个微服务数据
public class UserDetailService {
    public UserDetail getUserDetail(Long userId) {
        CompletableFuture<User> userFuture = CompletableFuture
            .supplyAsync(() -> userClient.getUser(userId));

        CompletableFuture<List<Order>> ordersFuture = CompletableFuture
            .supplyAsync(() -> orderClient.getOrders(userId));

        CompletableFuture<Credit> creditFuture = CompletableFuture
            .supplyAsync(() -> creditClient.getCredit(userId));

        CompletableFuture.allOf(userFuture, ordersFuture, creditFuture)
            .join();

        return new UserDetail(
            userFuture.join(),
            ordersFuture.join(),
            creditFuture.join()
        );
    }
}

2. 多级缓存

java 复制代码
public class MultiLevelCache {
    public CompletableFuture<String> get(String key) {
        // L1 缓存 → L2 缓存 → 数据库
        return CompletableFuture
            .supplyAsync(() -> l1Cache.get(key))
            .thenCompose(result -> {
                if (result != null) {
                    return CompletableFuture.completedFuture(result);
                }
                // L1 miss → 查 L2
                return CompletableFuture.supplyAsync(() -> l2Cache.get(key));
            })
            .thenCompose(result -> {
                if (result != null) {
                    // L2 hit → 回填 L1
                    l1Cache.put(key, result);
                    return CompletableFuture.completedFuture(result);
                }
                // L2 miss → 查数据库
                return CompletableFuture.supplyAsync(() -> database.query(key));
            })
            .thenApply(result -> {
                if (result != null) {
                    l1Cache.put(key, result);
                    l2Cache.put(key, result);
                }
                return result;
            });
    }
}

3. 重试机制

java 复制代码
public <T> CompletableFuture<T> withRetry(Supplier<T> task, int maxRetries) {
    CompletableFuture<T> future = CompletableFuture.supplyAsync(task);
    for (int i = 0; i < maxRetries; i++) {
        future = future.exceptionallyCompose(ex -> {
            log.warn("第 {} 次重试", i + 1);
            return CompletableFuture.supplyAsync(task);
        });
    }
    return future;
}

八、常见陷阱

1. 默认线程池问题

java 复制代码
// ❌ 不指定线程池 → 使用 ForkJoinPool.commonPool()
// 所有 CompletableFuture 共享 → 一个任务阻塞影响全局
CompletableFuture.supplyAsync(() -> {
    Thread.sleep(10000); // 阻塞 commonPool 线程
    return "data";
});

// ✅ 使用自定义线程池
ExecutorService pool = Executors.newFixedThreadPool(20);
CompletableFuture.supplyAsync(() -> {
    Thread.sleep(10000);
    return "data";
}, pool); // 指定线程池

2. get() 阻塞

java 复制代码
// ❌ get() 阻塞当前线程
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> fetchData());
String result = f.get(); // 阻塞! 失去异步优势

// ✅ 链式编排,避免阻塞
CompletableFuture.supplyAsync(() -> fetchData())
    .thenAccept(data -> process(data));

3. 异常被吞

java 复制代码
// ❌ 不处理异常 → 异常被吞
CompletableFuture.supplyAsync(() -> { throw new RuntimeException(); });
// 异常被 ForkJoinPool 吞掉!

// ✅ 添加异常处理
CompletableFuture.supplyAsync(() -> { throw new RuntimeException(); })
    .exceptionally(ex -> {
        log.error("异步任务失败", ex);
        return null;
    });

九、常见面试追问

Q1:CompletableFuture 和 Future 有什么区别?

Future 只能 get() 阻塞获取结果,不支持链式操作、组合、异常处理。CompletableFuture 支持链式编排(thenApply/thenCompose)、组合(allOf/anyOf)、异常处理(exceptionally)、超时控制(orTimeout)。

Q2:thenApply 和 thenCompose 的区别?

thenApply 是 map 操作(T → R),thenCompose 是 flatMap 操作(T → CompletableFuture)。thenCompose 用于串联多个异步操作,避免嵌套 CompletableFuture。

Q3:CompletableFuture 默认用什么线程池?

ForkJoinPool.commonPool()。它的并行度为 CPU 核心数 - 1。如果有阻塞操作建议使用自定义线程池。

Q4:allOf 返回什么?

返回 CompletableFuture,不包含结果。需要分别调用各 Future 的 join() 获取结果。

十、总结

arduino 复制代码
CompletableFuture 核心 API:

  创建: supplyAsync / runAsync / completedFuture
  转换: thenApply (map) / thenCompose (flatMap)
  消费: thenAccept / thenRun
  组合: thenCombine / allOf / anyOf / applyToEither
  异常: exceptionally / handle / whenComplete
  超时: orTimeout / completeOnTimeout (JDK 9+)

  设计理念:
    链式编排 → 避免回调地狱
    异步组合 → 并行执行提升吞吐
    异常处理 → 优雅恢复
    线程池 → 避免默认池阻塞

CompletableFuture 是 Java 异步编程的核心工具。理解链式编排、组合操作和异常处理,就能写出优雅高效的异步代码。在微服务场景中,并行调用多个服务可以大幅提升接口响应速度。

相关推荐
瑞雪兆丰年兮26 分钟前
JavaWeb-day14-SpringBootWeb原理篇
java·开发语言·spring boot·spring·bean·mybatis·javaweb
神仙别闹39 分钟前
基于 Java GUI 贪心策略直接搜索算法和极大极小博弈树算法的智能人机博弈五子棋游戏
java·毕业设计·gui·贪心策略·极大极小博弈树
Flynn Liang2 小时前
RAG 评估体系:黄金集、检索指标与 LLM-as-Judge 落地
java·langchain·rag·评估·langchain4j·检索指标
SimonKing3 小时前
Workbuddy太占C盘空间了,写个迁移工具迁走10G的数据
java·后端·程序员
Hvitur3 小时前
Java - Lambda
java·开发语言·python
apcipot_rain8 小时前
C++转Java日记(06)
java·开发语言
金銀銅鐵13 小时前
带您看懂字节码指令(一):一帧里有什么
java·后端·ai编程
nice_lcj52015 小时前
【从零理解 Spring AI · 14】RAG 的四个环节:加载、切分、向量化、检索
java·spring boot·embedding·向量数据库·rag·spring ai·ai应用开发
学长毕设16 小时前
基于springboot科创竞赛信息平台-附源码
java·spring boot·后端