前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CompletableFuture原理及应用场景详解

CompletableFuture原理及应用场景详解

原创
作者头像
卷福同学
发布于 2025-03-30 04:46:31
发布于 2025-03-30 04:46:31
1810
举报
文章被收录于专栏:小白晋级大师小白晋级大师

1.应用场景

现在我们打开各个APP上的一个页面,可能就需要涉及后端几十个服务的API调用,比如某宝、某个外卖APP上,下面是某个外卖APP的首页。首页上的页面展示会关联很多服务的API调用,如果使用同步调用的方式,接口耗时完全不能满足需求,因此,需要用到异步调用的方式。

2.使用线程池的弊端

说起异步调用,我们通常是创建一个线程池来实现多个请求的并行调用,这样接口的整体耗时由执行时间最长的线程决定。

但是线程池存在的问题是资源利用率较低:

  • CPU资源大量浪费在阻塞等待上
  • CPU调度的线程数增加了,在上下文切换上的资源消耗更大了。而且线程本身也占用系统资源

3.CompletableFuture的特性

我们引入CompletableFuture对业务流程进行编排,降低依赖之间的阻塞。本文主要讲述CompletableFuture的使用和原理。并对比Future、CompletableFuture、RxJava、Reactor的特性

Future

CompletableFuture

RxJava

Reactor

Composable(可组合)

✔️

✔️

✔️

Asynchronous(异步)

✔️

✔️

✔️

✔️

Operator fusion(操作融合)

✔️

✔️

Lazy(延迟执行)

✔️

✔️

Backpressure(回压)

✔️

✔️

  • 可组合:将多个依赖操作通过不同方式进行编排,例如CompletableFuture提供thenCompose、thenCombine等方法,这些方法支持了可组合的特性
  • 操作融合:将数据流中的多个操作符以某种方式结合起来,进而降低开销
  • 延迟执行:操作不会立即执行,当收到明确指示时才会触发操作
  • 回压:异步阶段的处理速度跟不上,直接失败会导致大量数据丢失,这是需要反馈上游生产者降低调用量

RxJava和Reactor虽然功能更强大,但是学习成本也更高,我们选择学习成本较低的CompletableFuture

4 一个例子回顾Future

CompletableFuture是由Java 8引入的,在Java8之前我们一般通过Future实现异步,而Future是Java5新加的接口,提供异步并行计算的功能

  • Future只能通过阻塞或者轮询的方式获取结果,且不支持设置回调方法
  • Future.get()方法是阻塞调用获取结果,还提供了isDone方法,在程序中轮询这个方法可查询执行结果

创建任务方法类

代码语言:java
AI代码解释
复制
public class UserService {

    public String getUserInfo() throws InterruptedException {
        Thread.sleep(300L);
        return "getUserInfo() 返回结果";
    }

    public String getUserAddress() throws InterruptedException {
        Thread.sleep(500L);
        return "getUserAddress() 返回结果";
    }
}

创建Future测试

代码语言:java
AI代码解释
复制
public class FutureTest {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        UserService userService = new UserService();
        try {
            Long start = System.currentTimeMillis();
            Future<String> future1 = executor.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return userService.getUserInfo();
                }
            });
            Future<String> future2 = executor.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return userService.getUserAddress();
                }
            });
            String result1 = future1.get();
            System.out.println(result1);
            String result2 = future2.get();
            System.out.println(result2);

            System.out.println("两个任务执行耗时:" + (System.currentTimeMillis() - start) + " ms");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}

最后执行结果为:

代码语言:shell
AI代码解释
复制
getUserInfo() 返回结果
getUserAddress() 返回结果
两个任务执行耗时:505 ms

使用Future后任务的整体耗时,由最长的任务耗时决定

前面也说过,Future对结果的获取不友好,没有提供回调方法,只能阻塞或者轮询的方式。

Java8之前也可以用guava的ListenableFuture,来设置回调,但是这样又会导致臭名昭著的回调地狱(异步编程中因多层嵌套回调函数导致的代码可读性、可维护性急剧下降的现象),这里不展开了

5.CompletableFuture的使用

CompletableFuture实现了两个接口:Future和CompletionStage,Future用于异步计算,CompletionStage用于表示异步执行过程汇总的一个步骤Stage

5.1一个例子入门CompletableFuture

这里创建一个流程,多个任务之间存在依赖关系

根据依赖数量,可以分为:零依赖、一元依赖、二元依赖、多元依赖

5.1.1零依赖:创建异步任务

上面两个任务CF1、CF2就是零依赖,可以直接创建,主要有三种创建方式:

代码语言:java
AI代码解释
复制
        ExecutorService executor = Executors.newFixedThreadPool(5);
        UserService userService = new UserService();
        //1、使用runAsync或supplyAsync发起异步调用
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() ->
                userService.getUserInfo(), executor);
        //2、CompletableFuture.completedFuture()直接创建一个已完成状态的CompletableFuture
        CompletableFuture<String> cf2 = CompletableFuture.completedFuture("result2");
        //3、先初始化一个未完成的CompletableFuture,然后通过complete() completeExceptionally(),完成该CompletableFuture
        CompletableFuture<String> cf = new CompletableFuture<>();
        cf.complete("success");

5.1.2 一元依赖:依赖一个CF

任务的执行存在一个上游依赖,可以通过thenApply、thenAccept、thenCompose方法来实现

代码语言:java
AI代码解释
复制
CompletableFuture<String> cf3 = cf1.thenApply(result1 -> {
  //result1为CF1的结果
  //......
  return "result3";
});
CompletableFuture<String> cf5 = cf2.thenApply(result2 -> {
  //result2为CF2的结果
  //......
  return "result5";
});

5.1.3 二元依赖:依赖两个CF

上图中的CF4就是个二元依赖,它依赖CF1和CF2,我们通过thenCombine等回调来实现。代码如下:

代码语言:java
AI代码解释
复制
CompletableFuture<String> cf4 = cf1.thenCombine(cf2, (result1, result2) -> {
            //result1和result2分别为cf1和cf2的结果
            return "result4";
        });

5.1.4 多元依赖:依赖多个CF

CF6是多元依赖,这种关系可以通过allOfanyOf方法来实现:

  • allOf方法:多个依赖需全部完成
  • anyOf方法:任意一个依赖完成即可
代码语言:java
AI代码解释
复制
        //多元依赖
CompletableFuture<Void> cf6 = CompletableFuture.allOf(cf3, cf4, cf5);
CompletableFuture<String> result = cf6.thenApply(v -> {
            //这里的join并不会阻塞,因为传给thenApply的函数是在CF3、CF4、CF5全部完成时,才会执行 。
            String result3 = cf3.join();
            String result4 = cf4.join();
            String result5 = cf5.join();
            //根据result3、result4、result5组装最终result;
            return result3 + result4 + result5;
        });

6.CompletableFuture原理

CompletableFuture包含了两个volatile修饰的变量:result和stack

  • result存储当前CF的结果
  • stack表示当前CF完成后需要触发的依赖动作,依赖动作可以有多个,以栈形成存储,stack表示栈顶元素
代码语言:java
AI代码解释
复制
    volatile Object result;       // Either the result or boxed AltResult
    volatile Completion stack;    // Top of Treiber stack of dependent actions

Completion类本身是观察者的基类

被观察者:每个CF都是一个被观察者,stack中存储的是注册的所有观察者,当CF执行完成后,会弹栈stack,依次通知观察者。result用于存储CF执行的结果数据

观察者:回调方法如thenApply、thenAccept会生成一个Completion类型的对象,就是观察者。检查当前CF是否已完成,如果已完成则执行Completion,否则加入观察者链stack中

7.使用问题

7.1代码执行在哪个线程上?

CompletableFuture的组合操作都有同步和异步两种方法:

同步方法(即不带Async后缀的):

  • 如果注册时被依赖的操作已经执行完成,则直接由当前线程执行
  • 如果注册时被依赖操作未执行完,则由回调线程执行

异步方法(带Async后缀的):

  • 不传递线程池参数Executor时,由公共线程池CommonPool(CPU核数-1)执行
  • 传递时用的传入的指定线程池

7.2异步回调要传线程池

异步回调时强制传入线程池,并根据实际情况做线程池隔离

不传递时,使用的都是公共线程池CommonPool,容易形成性能瓶颈。手动传递线程池参数可以更方便调节参数,并给不同业务分配不同线程池,做到资源隔离

7.3 Future需要获取返回值,才能获取异常信息

代码语言:java
AI代码解释
复制
CompletableFuture<Void> future = CompletableFuture.supplyAsync(
     ......
)

  //如果不加get()方法这一行,看不到异常信息
  future.get();

Future需要获取返回值时,才能获取到异常信息,不加get()方法是看不到的。

CompletableFuture还提供了异常捕获回调exceptionally方法,相当于同步调用中的try/catch方法可获取异常

代码语言:java
AI代码解释
复制
public CompletableFuture<Integer> getCancelTypeAsync(long orderId) {
    CompletableFuture<WmOrderOpRemarkResult> remarkResultFuture = wmOrderAdditionInfoThriftService.findOrderCancelledRemarkByOrderIdAsync(orderId);//业务方法,内部会发起异步rpc调用
    return remarkResultFuture
      .exceptionally(err -> {//通过exceptionally 捕获异常,打印日志并返回默认值
         log.error("WmOrderRemarkService.getCancelTypeAsync Exception orderId={}", orderId, err);
         return 0;
      });
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
CompletableFuture原理与实践-外卖商家端API的异步化
总第509篇 2022年 第026篇 CompletableFuture由Java 8提供,是实现异步化的工具类,上手难度较低,且功能强大,支持通过函数式编程的方式对各类操作进行组合编排。相比于ListenableFuture,CompletableFuture有效提升了代码的可读性,解决了“回调地狱”的问题。本文主要讲述CompletableFuture的原理与实践,同时结合了美团外卖商家端API的异步化实战,希望能对从事相关开发的同学有所帮助或启发。 0 背景 1 为何需要并行加载 2 并行加载的实现
美团技术团队
2022/05/12
1.6K0
CompletableFuture原理与实践-外卖商家端API的异步化
异步编程利器 CompletableFuture 玩法详解
在上篇文章中,我们介绍了Future相关的用法,使用它可以获取异步任务执行的返回值。
Java极客技术
2024/01/17
3590
异步编程利器 CompletableFuture 玩法详解
异步编程CompletableFuture使用
CompletableFuture 是对 Future 的扩展, 提供了函数式编程的能力,简化了异步编程的复杂性。
leobhao
2023/03/08
4950
异步编程CompletableFuture使用
一网打尽:异步神器 CompletableFuture 万字详解!
CompletableFuture是jdk8的新特性。CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步会点、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。
搜云库技术团队
2023/09/18
2.7K0
一网打尽:异步神器 CompletableFuture 万字详解!
CompletableFuture深度解析
在异步编程中,我们经常需要处理各种异步任务和操作。Java 8引入的 CompletableFuture 类为我们提供了一种强大而灵活的方式来处理异步编程需求。CompletableFuture 类提供了丰富的方法和功能,能够简化异步任务的处理和组合。
BookSea
2024/01/11
4940
CompletableFuture深度解析
Java8 CompletableFuture 用法全解
1、thenCombine / thenAcceptBoth / runAfterBoth
全栈程序员站长
2022/09/15
1.6K0
Java8 CompletableFuture 用法全解
任务编排:CompletableFuture从入门到精通
最近遇到了一个业务场景,涉及到多数据源之间的请求的流程编排,正好看到了一篇某团介绍CompletableFuture原理和使用的技术文章,主要还是涉及使用层面。网上很多文章涉及原理的部分讲的不是特别详细且比较抽象。因为涉及到多线程的工具必须要理解原理,不然一旦遇到问题排查起来就只能凭玄学,正好借此梳理一下CompletableFuture的工作原理
阿珍
2023/03/28
6010
编排并发与响应式初步 发布于 2023
在前些阵子的《ThreadLocal与ScopedValue》文章中,已经详细地描述了ThreadLocal与ScopedValue的作用。不同的多线程应用环境造就了不一样的两个本地线程缓存方案,实际项目开发中仍然是与多线程环境相互结合才能发挥它们最大的作用。
DioxideCN
2023/10/21
4340
编排并发与响应式初步
                        
                            发布于
                            2023
优雅的并发编程-CompletableFuture
CompletableFuture 是 Java 8 引入的一个类,用于支持异步编程和非阻塞操作。它提供了一种简单而强大的方式来处理异步任务,可以轻松地实现并行、非阻塞的操作,并且提供了丰富的方法来处理任务的完成状态、异常情况以及多个任务之间的串联和组合。
关忆北.
2023/11/09
9120
优雅的并发编程-CompletableFuture
Java 异步调用实践
当用户进程调用了recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。所以,Blocking IO 的特点就是在 IO 执行的两个阶段都被 block 了。
PPPHUANG
2022/07/30
4.9K0
Java 异步调用实践
Java8使用CompletableFuture的部分方法
CompletableFuture的使用是为了异步编程,异步编程可以解决同步编程的性能瓶颈问题。也就是将同步操作变为了并行操作。 当我们有一大批数据需要处理的时候我们可以将这些数据分而治之,使用CompletableFuture通过线程池的多个线程进行异步执行。
袁新栋-jeff.yuan
2020/08/26
1.6K0
从 CompletableFuture 到异步编程
JDK 5 引入了 Future 模式。Future 接口是 Java 多线程 Future 模式的实现,在 java.util.concurrent 包中,可以来进行异步计算。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?如 Netty、Guava 分别扩展了 Java 的 Future 接口,方便异步编程。
BUG弄潮儿
2021/04/12
1.3K0
京东一面:说说 CompletableFuture 的实现原理和使用场景?我懵了。。
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/04/11
5490
京东一面:说说 CompletableFuture 的实现原理和使用场景?我懵了。。
京东一面:说说 CompletableFuture 的实现原理和使用场景?懵逼了。。
CompletableFuture是jdk1.8引入的实现类。扩展了Future和CompletionStage,是一个可以在任务完成阶段触发一些操作Future。简单的来讲就是可以实现异步回调。
肉眼品世界
2022/04/19
3090
京东一面:说说 CompletableFuture 的实现原理和使用场景?懵逼了。。
深入理解CompletableFuture:异步编程的利器
在现代 Java 应用程序开发中,异步编程已经成为提升系统性能和用户体验的重要手段。CompletableFuture 作为 Java 8 引入的异步编程工具,不仅提供了 Future 接口的增强版本,还支持函数式编程,使得异步任务的编排和组合变得更加灵活和直观。本文将深入探讨 CompletableFuture 的各种应用场景,帮助你更好地掌握这个强大的工具。
CoderJia
2024/12/21
1930
深入理解CompletableFuture:异步编程的利器
CompletableFuture探秘:解锁Java并发编程的新境界
在Java的世界中,异步编程是应对高并发的利器,而CompletableFuture则是这个工具箱中的瑰宝。就像是一把打开未来之门的钥匙,CompletableFuture为我们提供了更高效、更灵活的并发编程方式。让我们一同踏上这段深入CompletableFuture的实战之旅,发现异步编程的魔力。
一只牛博
2025/05/31
660
深度解析CompletableFuture:Java 异步世界的奇迹
上文我们可知:CompletableFuture 是 Java 8 引入用于支持异步编程和非阻塞操作的类。对于没有使用过CompletableFuture通过它这么长的名字就感觉到一头雾水,那么现在我们来一起解读一下它的名字。
关忆北.
2023/11/13
6510
深度解析CompletableFuture:Java 异步世界的奇迹
理解Java8里面CompletableFuture异步编程
其中第三个特性,就是今天我们想要聊的话题,正是因为CompletableFuture的出现,才使得使用Java进行异步编程提供了可能。
我是攻城师
2018/11/23
16.7K0
JUC从实战到源码:六千字详细学习CompletableFuture
上篇文章,我们学习了Future的基本使用,以及其优缺点,然而其缺点是更加突出的,这也就在jdk8的时候就引申出CompletableFuture,这个类更能够很好的解决了异步编程来使性能提升。然而这是如何从Future演变到CompletableFuture呢?这就是我们这章将要学习的内容。
怒放吧德德
2024/09/19
2840
你应该使用Java8 非阻塞异步API来优化你的系统了
上述代码可以实现我们想要的结果,但是不推荐,Thread 并没有进行相关的方法组合、依赖API,这种实现方式,到后边基本就成了回调地狱。
程序猿DD
2020/12/18
8660
推荐阅读
相关推荐
CompletableFuture原理与实践-外卖商家端API的异步化
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档