CompletableFuture

qinyelin
发布于 2026-08-25 / 5 阅读
0
0

CompletableFuture

CompletableFuture 异步编排

面试频率:★★★★☆
工作频率:★★★★★


⚡30 秒速记(复习只看这里)

🟢 一句话

CompletableFuture 是 Java 提供的异步编程工具,可以实现异步执行、任务串联、多个任务并行和结果合并。

核心:

异步执行

+

任务回调

+

任务组合

🟡 最常用的方法

runAsync

↓

异步执行,无返回值


supplyAsync

↓

异步执行,有返回值


thenApply

↓

拿上一步结果继续处理


allOf

↓

等待多个任务全部完成


join

↓

获取异步结果

🔴 最重要的一点

生产环境:

不建议直接:

CompletableFuture.supplyAsync(() -> query());

更推荐:

CompletableFuture.supplyAsync(
    () -> query(),
    executor
);

也就是:

使用自己配置的线程池,避免大量异步任务占用公共线程池。


📚 一、CompletableFuture 是什么?

CompletableFuture:

Java 8 提供的异步编程工具。

包:

java.util.concurrent.CompletableFuture

主要解决:

异步执行

↓

任务编排

↓

结果合并

二、为什么需要 CompletableFuture?

假设:

订单详情接口需要查询:

订单信息

用户信息

物流信息

如果串行:

查询订单 100ms

↓

查询用户 200ms

↓

查询物流 300ms

总时间大约:

600ms

如果三个任务互相不依赖:

可以并行:

        ┌→ 查询订单 100ms
请求 ───┼→ 查询用户 200ms
        └→ 查询物流 300ms

最终耗时大约:

300ms

而不是:

600ms

这就是 CompletableFuture 非常常见的使用场景。


三、Future 有什么问题?

以前可以:

Future<String> future =
        executor.submit(() -> queryUser());

获取结果:

String user = future.get();

问题:

future.get()

↓

当前线程阻塞

↓

等待异步任务完成

而且:

Future 对多个任务之间的组合、串联操作支持比较弱。


CompletableFuture 可以:

任务A完成

↓

自动执行任务B

↓

任务B完成

↓

处理结果

更适合复杂异步流程。


四、runAsync ⭐⭐⭐⭐

没有返回值

例如:

CompletableFuture.runAsync(() -> {

    sendEmail();

});

适合:

发送邮件

发送短信

记录日志

这些:

不关心返回结果的任务。


完整:

CompletableFuture<Void> future =
        CompletableFuture.runAsync(() -> {

            sendEmail();

        });

返回:

CompletableFuture<Void>

五、supplyAsync ⭐⭐⭐⭐⭐

有返回值

例如:

CompletableFuture<User> future =
        CompletableFuture.supplyAsync(() -> {

            return queryUser();

        });

适合:

查询数据库

调用接口

查询Redis

获取业务数据

例如:

User user = future.join();

获得结果。


六、runAsync 和 supplyAsync 区别

方法返回值使用场景
runAsync没有发短信、写日志
supplyAsync查询数据库、调用接口

记:

run

↓

只执行


supply

↓

执行 + 返回数据

七、thenApply:任务串联 ⭐⭐⭐⭐⭐

假设:

第一步:

查询用户

第二步:

根据用户查询订单

第二步依赖第一步。

可以:

CompletableFuture
        .supplyAsync(() -> queryUser())

        .thenApply(user -> {

            return queryOrder(user);

        });

流程:

queryUser()

↓

得到 User

↓

thenApply

↓

queryOrder(user)

↓

得到 Order

所以:

thenApply 可以拿到上一步的返回结果,然后继续处理,并返回新的结果。


八、thenAccept

如果:

需要上一步结果。

但是:

自己不需要返回结果。

例如:

CompletableFuture
        .supplyAsync(() -> queryUser())

        .thenAccept(user -> {

            sendEmail(user);

        });

流程:

查询User

↓

拿到User

↓

发送邮件

↓

结束

区别:

thenApply

有返回值


thenAccept

没有返回值

九、thenRun

如果:

既不关心上一步结果。

也没有返回值。

例如:

CompletableFuture
        .supplyAsync(() -> queryUser())

        .thenRun(() -> {

            System.out.println("执行完成");

        });

三者区别:

thenApply

拿结果
+
有返回值


thenAccept

拿结果
+
无返回值


thenRun

不拿结果
+
无返回值

十、多个任务并行 ⭐⭐⭐⭐⭐

例如:

订单详情接口:

需要:

订单

用户

物流

可以:

CompletableFuture<Order> orderFuture =
        CompletableFuture.supplyAsync(() -> {

            return queryOrder();

        });


CompletableFuture<User> userFuture =
        CompletableFuture.supplyAsync(() -> {

            return queryUser();

        });


CompletableFuture<Logistics> logisticsFuture =
        CompletableFuture.supplyAsync(() -> {

            return queryLogistics();

        });

三个任务:

同时执行

而不是:

一个执行完

再执行下一个

十一、allOf ⭐⭐⭐⭐⭐

如果需要:

等待所有任务完成。

使用:

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

流程:

订单任务 ─────┐

用户任务 ─────┼→ allOf → 全部完成

物流任务 ─────┘

注意:

allOf() 本身:

不会直接把三个任务结果组合起来。

所以通常:

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


Order order = orderFuture.join();

User user = userFuture.join();

Logistics logistics = logisticsFuture.join();

十二、anyOf

和 allOf 不同。

allOf

所有任务完成

才结束。


anyOf

任意一个任务完成

就结束。

例如:

CompletableFuture.anyOf(
        future1,
        future2,
        future3
).join();

十三、join 和 get 区别 ⭐⭐⭐⭐

两个都可以:

获取异步结果。


get()

future.get();

需要处理:

检查型异常。

例如:

try {

    User user = future.get();

} catch (Exception e) {

}

join()

User user = future.join();

不要求强制捕获检查型异常。

代码更简洁。


所以业务代码:

经常看到:

join()

十四、CompletableFuture 使用哪个线程池? ⭐⭐⭐⭐⭐

如果:

CompletableFuture.supplyAsync(() -> queryUser());

没有指定线程池。

默认使用:

ForkJoinPool.commonPool()

也就是:

JVM 公共线程池。


问题:

如果项目中很多地方:

都使用公共线程池。

例如:

业务A

业务B

业务C

业务D

全部:

ForkJoinPool.commonPool

一个业务任务特别慢:

可能影响其他业务。


所以生产环境推荐:

指定自己的线程池。


十五、配合 ThreadPoolTaskExecutor ⭐⭐⭐⭐⭐

你昨天刚学:

ThreadPoolTaskExecutor

Spring项目可以配置:

@Bean
public ThreadPoolTaskExecutor orderExecutor() {

    ThreadPoolTaskExecutor executor =
            new ThreadPoolTaskExecutor();

    executor.setCorePoolSize(10);

    executor.setMaxPoolSize(20);

    executor.setQueueCapacity(100);

    executor.setThreadNamePrefix("order-");

    executor.initialize();

    return executor;
}

使用:

CompletableFuture<User> future =
        CompletableFuture.supplyAsync(
            () -> queryUser(),
            orderExecutor
        );

这样:

CompletableFuture

↓

ThreadPoolTaskExecutor

↓

ThreadPoolExecutor

↓

线程执行任务

十六、为什么推荐自己配置线程池?

因为可以控制:

核心线程数

最大线程数

队列容量

拒绝策略

线程名称

例如:

订单业务

↓

order-thread-pool


短信业务

↓

sms-thread-pool

互相隔离。


如果短信服务卡死:

不会把订单线程池全部占满。

这叫:

线程池隔离。


十七、异常处理 ⭐⭐⭐⭐

异步任务:

很容易出现一个问题:

线程里面异常了

↓

主线程不知道

CompletableFuture 可以:

CompletableFuture
        .supplyAsync(() -> queryUser())

        .exceptionally(e -> {

            log.error("查询用户失败", e);

            return null;

        });

流程:

正常

↓

返回User


异常

↓

exceptionally

↓

返回兜底结果

还可以:

.handle((result, exception) -> {

    if (exception != null) {

        return null;

    }

    return result;

});

十八、工作场景:订单详情接口

例如接口:

GET /order/detail

需要:

订单信息

用户信息

物流信息

优惠券信息

这些任务:

互相不依赖。

可以并行:

CompletableFuture<Order> orderFuture =
        CompletableFuture.supplyAsync(
            () -> queryOrder(),
            executor
        );


CompletableFuture<User> userFuture =
        CompletableFuture.supplyAsync(
            () -> queryUser(),
            executor
        );


CompletableFuture<Logistics> logisticsFuture =
        CompletableFuture.supplyAsync(
            () -> queryLogistics(),
            executor
        );


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

最后:

Order order = orderFuture.join();

User user = userFuture.join();

Logistics logistics = logisticsFuture.join();

组装:

OrderDetailVO vo = new OrderDetailVO();

vo.setOrder(order);

vo.setUser(user);

vo.setLogistics(logistics);

返回前端。


十九、什么时候不适合 CompletableFuture?

如果任务本身:

必须严格按照顺序执行。

例如:

创建订单

↓

拿到订单ID

↓

扣库存

↓

保存支付记录

后面的任务:

依赖前面的结果。

这种不能简单全部并行。


另外:

非常简单的业务:

没必要为了异步而异步。

否则:

代码复杂

异常处理复杂

线程池管理复杂

二十、CompletableFuture 和 @Async 区别

@Async

Spring提供:

@Async
public void sendEmail() {

}

特点:

简单。

适合:

单个方法异步

CompletableFuture

Java提供。

适合:

多个异步任务

↓

任务组合

↓

结果合并

简单理解:

@Async

↓

我要让这个方法异步


CompletableFuture

↓

我要编排多个异步任务

💼 工作场景总结

适合:

一个接口需要查询多个互不依赖的数据

例如:

商品详情:

商品信息
库存
评价
推荐商品
优惠券

可以:

       ┌→ 商品
       │
请求 ──┼→ 库存
       │
       ├→ 评价
       │
       └→ 优惠券

↓

allOf

↓

组装结果

↓

返回

减少接口响应时间。


🎤 面试回答(30秒)

CompletableFuture 是 Java 8 提供的异步编程工具,可以实现异步任务执行、任务串联、多个任务并行以及结果合并。runAsync 用于没有返回值的任务,supplyAsync 用于有返回值的任务,thenApply 可以串联任务,allOf 可以等待多个任务全部完成。CompletableFuture 默认使用 ForkJoinPool.commonPool,生产环境通常建议指定自己的线程池,避免不同业务之间相互影响。


🎯 面试追问

Q1:CompletableFuture 是干什么的?

答:

异步执行和异步任务编排。


Q2:runAsync 和 supplyAsync 区别?

答:

runAsync

无返回值


supplyAsync

有返回值

Q3:thenApply 是什么?

答:

拿到上一个任务的结果,继续执行下一个任务,并返回新的结果。


Q4:allOf 是什么?

答:

等待多个 CompletableFuture 全部执行完成。


Q5:allOf 会返回所有任务结果吗?

答:

不会。

allOf 主要负责等待全部完成。

具体结果:

需要分别通过:

future.join();

获取。


Q6:join 和 get 区别?

答:

都可以获取结果。

get 需要处理检查型异常。

join 不要求强制捕获检查型异常。


Q7:CompletableFuture 默认使用什么线程池?

答:

ForkJoinPool.commonPool()

Q8:为什么生产环境推荐自己配置线程池?

答:

避免所有异步任务共用公共线程池,造成业务互相影响,同时可以控制核心线程数、最大线程数、队列和拒绝策略。


Q9:CompletableFuture 和 @Async 区别?

答:

@Async 适合简单的方法异步。

CompletableFuture 更适合多个异步任务的编排和结果合并。


⚠️ 工作踩坑

1. 不指定线程池

CompletableFuture.supplyAsync(() -> query());

可能:

所有业务共用:

ForkJoinPool.commonPool

生产环境最好:

指定业务线程池。


2. 忘记处理异常

异步线程发生异常:

可能不容易发现。

需要:

exceptionally()

或者:

handle()

处理。


3. 所有任务都使用 CompletableFuture

错误。

如果任务之间:

有严格依赖关系。

不能为了并行强行并行。


4. 线程池配置过大

CompletableFuture 并不是:

线程越多越快

线程太多:

上下文切换增加

↓

CPU压力增加

❓ 自测

  1. CompletableFuture 是什么?

  2. 为什么需要 CompletableFuture?

  3. runAsync 和 supplyAsync 有什么区别?

  4. thenApply 是干什么的?

  5. thenAccept 和 thenRun 有什么区别?

  6. allOf 是什么?

  7. allOf 会直接返回所有任务结果吗?

  8. join 和 get 有什么区别?

  9. CompletableFuture 默认使用什么线程池?

  10. 为什么生产环境推荐指定自己的线程池?

  11. CompletableFuture 和 ThreadPoolTaskExecutor 是什么关系?

  12. CompletableFuture 和 @Async 有什么区别?


评论