Q1:CompletableFuture 相比 Future 有哪些优势

Future 就像打电话订外卖,打完电话就干等着,外卖到了才去拿。CompletableFuture 则是点完餐后手机能实时通知、还能组合多个订单、超时自动退款——智能得多。

// Future 的痛点:想等 A 完成后再执行 B,代码很难写
new Thread(() -> {
    Integer a = future.get();      // 必须先阻塞等待
    Integer b = computeB(a);       // 再执行 B
}).start();

// CompletableFuture:链式调用,清晰简洁
CompletableFuture.supplyAsync(() -> computeA())
    .thenApply(a -> computeB(a))   // A 完成后自动执行 B
    .thenAccept(b -> System.out.println(b));  // 消费最终结果

Q2:runAsync 和 supplyAsync 的区别是什么?各自的适用场景?

// runAsync:无返回值,适合"发后不管"的场景
CompletableFuture<Void> logFuture = CompletableFuture.runAsync(() -> {
    logService.saveLog(userId, action);  // 不需要返回值
}, executor);

// supplyAsync:有返回值,适合需要获取结果的场景
CompletableFuture<UserInfo> userFuture = CompletableFuture.supplyAsync(() -> {
    return userService.getUser(userId);  // 返回用户信息
}, executor);

UserInfo user = userFuture.join();  // 获取结果

Q3:thenApply、thenAccept、thenRun 的使用场景分别是什么?

是否需要上游的结果?
  ├─ 是 → 是否需要返回新结果?
  │        ├─ 是 → thenApply
  │        └─ 否 → thenAccept
  └─ 否 → thenRun

Q4:exceptionally 和 handle 的区别是什么?什么时候用哪个?

核心回答要点

方法

触发时机

能否修改结果

类比

exceptionally

仅异常时触发

能(返回默认值)

保险理赔:出事了才赔

handle

成功或异常都触发

能(统一处理)

体检报告:无论好坏都出报告

whenComplete

成功或异常都触发

不能(只观察)

监控摄像头:只记录不干预

// exceptionally:只在异常时兜底
CompletableFuture<String> f1 = CompletableFuture
    .<String>supplyAsync(() -> { throw new RuntimeException("失败"); })
    .exceptionally(ex -> "默认值");  // 异常时返回默认值
// 正常流程不受影响

// handle:无论成功失败都处理
CompletableFuture<String> f2 = CompletableFuture
    .supplyAsync(() -> "Success")
    .handle((result, ex) -> {
        if (ex != null) {
            return "异常:" + ex.getMessage();
        }
        return "结果:" + result;
    });
// 成功时 result="Success", ex=null
// 失败时 result=null, ex=异常

// whenComplete:只观察不修改
CompletableFuture<String> f3 = CompletableFuture
    .supplyAsync(() -> "Hello")
    .whenComplete((result, ex) -> {
        log.info("完成,结果:{}", result);  // 不能修改 result
    });
// f3 的结果仍然是 "Hello"

选型建议

  • • 只需要异常兜底 → exceptionally

  • • 需要统一处理成功和失败 → handle

  • • 只需要观察完成状态(如日志监控)→ whenComplete

Q5:如何组合多个 CompletableFuture?thenCombine、allOf、anyOf 各自的特点?

方法

等待条件

结果数量

返回值

thenCombine

两个都完成

2 个

合并后的新结果

allOf

所有都完成

N 个

CompletableFuture<Void>

(需手动获取各结果)

anyOf

任意一个完成

1 个

最先完成的那个结果

代码示例

// thenCombine:两个独立任务都完成后合并
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(f2, (s1, s2) -> s1 + " " + s2);
// "Hello World"

// allOf:等待所有任务完成(适合批量场景)
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "A");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "B");
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> "C");

CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.join();  // 等所有完成
String r1 = f1.join();  // 手动获取各结果
String r2 = f2.join();
String r3 = f3.join();

// anyOf:等最快的那个(适合冗余请求)
CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> "快的");
CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(1000);
    return "慢的";
});
Object result = CompletableFuture.anyOf(fast, slow).join();  // "快的"

实战场景

  • • thenCombine:两个数据源都查完后合并展示(如用户信息 + 订单信息)

  • • allOf:批量查询 100 个订单,等全部完成后统一处理

  • • anyOf:同时请求主备两个数据源,谁快用谁

Q6:thenApply 和 thenApplyAsync 的区别是什么?什么时候该用异步版本?

核心回答要点

方法

执行线程

性能

适用场景

thenApply

和上游任务同一个线程

高(无线程切换)

大部分场景

thenApplyAsync

新线程(默认 ForkJoinPool 或指定线程池)

低(有线程切换开销)

需要线程隔离、避免长任务阻塞

// thenApply:和上游同一个线程
String thread1 = CompletableFuture
    .supplyAsync(() -> {
        System.out.println("上游线程:" + Thread.currentThread().getName());
        return "Hello";
    })
    .thenApply(s -> {
        System.out.println("thenApply线程:" + Thread.currentThread().getName());
        return s + " World";
    }).join();
// 两个打印的线程名相同

// thenApplyAsync:使用新线程
String thread2 = CompletableFuture
    .supplyAsync(() -> "Hello")
    .thenApplyAsync(s -> {
        System.out.println("异步线程:" + Thread.currentThread().getName());
        return s + " World";
    }, executor)  // 可指定线程池
    .join();
// 可能在不同的线程执行

选型建议

  • • 默认使用 thenApply(无额外开销,适合大部分场景)

  • • 下游任务耗时较长、需要与上游隔离时才用 thenApplyAsync

  • • 如果 CPU 密集型任务,用 thenApply 避免上下文切换;如果 IO 密集型,可以用 thenApplyAsync 释放上游线程

常见误区:最初以为带 Async 后缀的性能更好,实际上恰恰相反——线程切换有成本,能不用就不用。

Q7:如何为 CompletableFuture 设置超时?JDK8 和 JDK9+ 分别怎么做?

JDK9+ 原生支持

// 超时抛出 TimeoutException
CompletableFuture<String> f1 = CompletableFuture
    .supplyAsync(() -> callExternalService())
    .orTimeout(5, TimeUnit.SECONDS);

// 超时返回默认值(不抛异常)
CompletableFuture<String> f2 = CompletableFuture
    .supplyAsync(() -> callExternalService())
    .completeOnTimeout("降级默认值", 5, TimeUnit.SECONDS);

JDK8 手动实现

public static <T> CompletableFuture<T> timeout(
        CompletableFuture<T> future, long timeout, TimeUnit unit) {

    CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    // 超时后标记为异常完成
    scheduler.schedule(() -> {
        timeoutFuture.completeExceptionally(new TimeoutException("超时"));
    }, timeout, unit);

    // 原始任务完成后覆盖超时
    future.whenComplete((result, ex) -> {
        if (ex != null) {
            timeoutFuture.completeExceptionally(ex);
        } else {
            timeoutFuture.complete(result);
        }
    });

    return timeoutFuture;
}

Q8:为什么建议 CompletableFuture 使用自定义线程池而不是默认的 ForkJoinPool?

核心回答要点

默认线程池 ForkJoinPool.commonPool() 存在以下问题:

问题 1:全局共享,互相影响

ForkJoinPool 是所有未指定线程池的 CompletableFuture 共享的。如果一个业务模块的异步任务把线程池占满了,其他模块的异步任务也会被阻塞。

问题 2:线程数有限

ForkJoinPool 的默认并行度等于 CPU 核心数减 1。对于 IO 密集型任务来说远远不够。

问题 3:线程命名不规范,问题排查困难

ForkJoinPool 的线程名是 ForkJoinPool.commonPool-worker-N,出问题后很难定位是哪个业务模块。

问题 4:拒绝策略不可控

共享线程池的拒绝策略是固定的,不同业务可能需要不同的策略

// ✅ 使用自定义线程池
ExecutorService orderExecutor = new ThreadPoolExecutor(
    10,                          // 核心线程数
    20,                          // 最大线程数
    60L, TimeUnit.SECONDS,       // 空闲超时
    new ArrayBlockingQueue<>(100),  // 有界队列
    new ThreadFactory() {
        private final AtomicInteger count = new AtomicInteger(0);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("order-async-" + count.incrementAndGet());
            return t;
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy()  // 队列满时由调用线程执行
);

CompletableFuture.supplyAsync(() -> orderService.query(), orderExecutor);

线程池参数配置原则

任务类型

核心线程数

最大线程数

队列

CPU 密集

CPU 核心数

CPU 核心数 + 1

无界或有界

IO 密集

CPU 核心数 × 2

CPU 核心数 × 2 ~ 4 倍

有界(防止 OOM)

混合型

分开两个池

分别配置

有界

Q9:能说出至少 3 个 CompletableFuture 的实战应用场景吗?

场景 1:多服务并行调用(电商详情页)

// 串行:300ms
// 并行:约 100ms
CompletableFuture<UserInfo> userF = CompletableFuture.supplyAsync(
    () -> userService.getUser(userId), executor);
CompletableFuture<OrderInfo> orderF = CompletableFuture.supplyAsync(
    () -> orderService.getOrder(userId), executor);

Result result = userF
    .thenCombine(orderF, (user, order) -> combine(user, order))
    .get(200, TimeUnit.MILLISECONDS);

场景 2:异步日志记录(非阻塞)

// 主流程不等待日志写完
CompletableFuture.runAsync(() -> {
    logService.save(userId, action);
}).exceptionally(ex -> {
    // 日志失败不影响主流程
    System.err.println("日志失败:" + ex.getMessage());
    return null;
});

场景 3:批量数据处理(订单批量查询)

List<Long> orderIds = Arrays.asList(1L, 2L, 3L, 4L, 5L);

List<CompletableFuture<Order>> futures = orderIds.stream()
    .map(id -> CompletableFuture.supplyAsync(
        () -> orderService.getById(id), executor))
    .collect(Collectors.toList());

List<Order> orders = CompletableFuture.allOf(
        futures.toArray(new CompletableFuture[0]))
    .thenApply(v -> futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList()))
    .join();

场景 4:链式处理订单(创建 → 支付 → 发货 → 通知)

CompletableFuture<Order> orderFuture = CompletableFuture
    .supplyAsync(() -> orderService.create(order), executor)
    .thenApply(order -> { paymentService.pay(order); return order; })
    .thenApply(order -> { shipmentService.ship(order); return order; })
    .thenAccept(order -> notificationService.notify(order))
    .exceptionally(ex -> { log.error("失败", ex); return null; });

Q10:thenCompose 和 thenCombine 的区别是什么?

维度

thenCompose

thenCombine

任务关系

串行依赖(B 依赖 A 的结果)

并行独立(两个任务互不影响)

执行方式

等 A 完成后才启动 B

A 和 B 同时启动

参数

接收一个返回 CompletableFuture 的函数

接收另一个 CompletableFuture 和合并函数

类比

接力赛:等前一棒跑完下一棒才起跑

拔河:两队同时发力

代码对比
// thenCompose:串行,B 依赖 A 的结果
// 场景:先查用户,再用用户ID查订单
CompletableFuture<Order> order = CompletableFuture
    .supplyAsync(() -> userService.getUser(userId))
    .thenCompose(user -> CompletableFuture.supplyAsync(
        () -> orderService.getOrders(user.getId())));

// thenCombine:并行,两个独立任务
// 场景:同时查用户信息和订单信息,都查完后合并
CompletableFuture<DemoDetail> detail =
    CompletableFuture.supplyAsync(() -> userService.getUser(userId))
        .thenCombine(
            CompletableFuture.supplyAsync(() -> orderService.getOrders(userId)),
            (user, orders) -> new DemoDetail(user, orders));

更多推荐