java8已经在日常开发编码中非常普遍了,掌握运用好它可以在开发中运用几行精简代码就可以完成所需功能。
今天将介绍CompletableFuture的在生产环境如何使用实践。CompletableFuture类作为Java 8 Concurrency API改进而引入,熟悉的同学应该了解在Java 9 也有对CompletableFuture有一些改进,橘子之后再进入讲解。
阅读这篇文章需要知道的前置知识点有,函数式编程,线程池原理等。还不熟悉的同学可以看看之前的文章,话不多说,开始吧。

为了更好的表达,我们结合例子讲解,假设今天小橘收到TL任务,要求完成实时拉取数据的功能,完成后告知拉取完成。假设拉取数据需要从A,B,C三个服务中获取,拉取完成推送需要调用D服务。

需求变更1:拉取数据需要从E服务获取,但是会依赖从A服务获取的结果。
需求变更2:从A服务一次能拉去一万+数据,但是E服务的性能支撑不了大调用,在Provider端有限流兜底。
需求变更3:拉取数据过程中需要保证数据完整性,不能出现统计错误。

为什么使用CompletableFuture

橘友们说了,这个可以用jdk5.0提供的Future来实现,我们将拉取数据需要用到的从A,B ,C三个服务接口放到FutureTask中,异步的去执行获取数据结果,然后再同步调用D服务。
OK,简单实现这个功能没有问题,但是有什么缺陷,需要怎么可以改进嘛?
我们通过源码注释可以看到Future类返回的结果需要阻塞等待get方法返回结果,它提供了isDone()方法检测异步计算是否已经结束,get()方法等待异步操作结束,以及获取计算的结果。等到所有Future任务完成,通知线程获取结果并合并。

从性能上,需要等待 Future 集合中的所有任务都完成(此需求没问题,接着往下看), 从健壮性上,Futrue接口没有方法去进行计算组合或者处理可能出现的错误。从功能扩展上,Future接口无法进行多个异步计算之间相互独立,同时第二个又依赖于第一个的结果。而今天的主角CompletableFuture都可以满足上述功能,具有大约50种不同的构成,结合,执行异步计算步骤和处理错误。(全部学习完所有方法是不现实的,掌握灵魂和核心方法即可依法炮制)

CompletableFuture API 使用

API太多,简单列举。读者自行学习即可,本文重点不在介绍api

/**
   任务 A 执行完执行 B,执行 B 不需要依赖 A 的结果同时 B 不返回结果。
*/
CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}); 
/**
   任务 A 执行完执行 B,B 执行依赖 A 结果同时 B 不返回结果
*/
CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {});
/**
   任务 A 执行完执行 B,B 执行依赖 A 结果同时 B 返回结果
*/
CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB");
CompletableFuture<String> completableFuture 
  = CompletableFuture.supplyAsync(() -> "orange")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " csong"));
//true
assertEquals("orangecsong", completableFuture.get());

你的疑问:该thenCompose方法不和thenApply一样实现结果合并计算嘛?

刚学习时候确实有点迷惑,其实他们的内部形式是不一样的,它们与Java 8中可用的Stream和Optional类的map和flatMap方法是有着类似的设计思路在里面的。都是接收一个CompletableFuture并将其应用于计算结果,但thenCompose(flatMap)方法接收一个函数,该函数返回相同类型的另一个CompletableFuture对象。

CompletableFuture<String> completableFuture 
  = CompletableFuture.supplyAsync(() -> "orange")
    .thenCombine(CompletableFuture.supplyAsync(
      () -> " chizongzi"), (s1, s2) -> s1 + s2));

assertEquals("orange chizongzi", completableFuture.get());

thenCombine方法旨在当你想要使用多个计算结果时,而后续的处理同时需要依赖返回值,第一个计算结果返回 "orange",第二个计算结果返回 "chizongzi",对结果进行拼接,那么结果就是"orange chizongzi" 啦。你可能会问如果结果无需处理呢?thenAcceptBoth将可以实现你的功能。那么它和thenApply的区别又是啥呢?
thenCompose()方法是使用前一个Future作为参数。它会直接使结果变新的Future,而不是我们在thenApply()中到的嵌套Future,而是用来连接两个CompletableFuture,是生成一个新的CompletableFuture,因此,如果想要继续嵌套链接CompletableFuture 方法,那么最好使用thenCompose()。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){...}

当我们需要并行执行多个任务时,我们通常希望等待所有它们执行,然后处理它们的组合结果。CompletableFuture提供了allOf静态方法允许等待所有的完成任务,但是它返回类型是CompletableFuture 。局限性在于它不会返回所有任务的综合结果。相反,你必须手动从Futures获取结果。那么怎么解决呢,CompletableFuture提供了join()可以解决,这里小橘用Stream实现同样可以的。

String multiFutures= Stream.of(future1, future2, future3)
  .map(CompletableFuture::join)
  .collect(Collectors.joining(" "));

assertEquals("Today is sun", multiFutures);

那么 CompletableFuture 针对异常是如何处理的呢?

public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
CompletableFuture.supplyAsync(() -> "resultA")
    .thenApply(resultA -> resultA + " resultB")
    .thenApply(resultB -> resultB + " resultC")

//如果resultA,resultB,resultC在获取中有异常

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    throw new RuntimeException();
}).exceptionally(ex -> "errorResultA")
  .thenApply(resultA -> resultA + " resultB")
  .thenApply(resultB -> resultB + " resultC")

上面的代码中,任务 A 抛出异常,然后通过exceptionally() 方法处理了异常,并返回新的结果,这个新的结果将传递给任务 B。如果inovke future.join方法结果将会输出 "errorResultA resultB result C"

上述方法基本就是底层函数式api的使用,聪明的橘友们实践起来吧!

CompletableFuture 例子

Talk is cheap , show me code。自从上篇 你还在担心rpc接口超时吗 文章末尾讲述大批量调用,其中是顺序invoke调用,其实我们分析,异步调用利用CompletableFuture需要怎么实现呢?

/**
 * @Description:
 * @author: orangeCs
 * @create: 2020-06-25
 */
public class AsyncInvokeUtil {

    private AsyncInvokeUtil() {}

    /**
     * @param paramList 源数据 (需处理数据载体)
     * @param buildParam 中转函数 (获取的结果做一层trans,来满足调用服务条件)
     * @param transParam 中转函数 (获取的结果做一层trans,来满足调用服务条件)
     * @param processFunction 中转处理函数
     * @param size 分批大小
     * @param executorService 暴露外部自定义实现线程池(demo没判空,可以做成非必传)
     * @param <R>
     * @param <T>
     * @param <P>
     * @param <k>
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static <R, T, P, k> List<R> partitionAsyncInvokeWithRes(List<T> paramList,
                                                                      Function<List<T>, P> buildParam,
                                                                      Function<P, List<k>> transParam,
                                                                      Function<List<k>,List<R>> processFunction,
                                                                      Integer size,
                                                                      ExecutorService executorService) throws ExecutionException, InterruptedException {
        List<CompletableFuture<List<R>>> completableFutures = Lists.partition(paramList, size).stream()
                .map(buildParam)
                .map(transParam)
                .map(eachList -> CompletableFuture.supplyAsync(() ->
                        processFunction.apply(eachList), executorService))
                .collect(Collectors.toList());
        //get
        CompletableFuture<Void> finishCompletableFuture = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]));
        finishCompletableFuture.get();
        return completableFutures.stream().map(CompletableFuture::join)
                .filter(Objects::nonNull).reduce(new ArrayList<>(), (resList1, resList2) -> {
                    resList1.addAll(resList2);
                    return resList1;
                });
    }

}       

仅仅这一篇文章是不够的,任何知识都是长期积累,反复思考才能变成自己的东西,在浮躁的社会,我们年轻人切勿浮躁,今天介绍到这里了,喜欢博主的朋友们记得点个关注哦。

> 本文由博客群发一文多发等运营工具平台 [OpenWrite](https://openwrite.cn?from=article_bottom) 发布

听说你还不知道CompletableFuture?的更多相关文章

  1. 听说你还不知道Spring是如何解决循环依赖问题的?

    Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题. 其实笔者本人对这类框架源码题还是持一定的怀疑态度的. 如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null, ...

  2. 听说你还不知道Java代码是怎么运行的?

    作为一名Java程序员,我们需要知道Java代码是怎么运行的.最近复习了深入理解Java虚拟机这本书,做了一下笔记,希望对大家有帮助,如果有不正确的地方,欢迎提出,感激不尽. java 代码运行主要流 ...

  3. JDK15就要来了,你却还不知道JDK8的新特性!

    微信搜「烟雨星空」,白嫖更多好文. 现在 Oracle 官方每隔半年就会出一个 JDK 新版本.按时间来算的话,这个月就要出 JDK15 了.然而,大部分公司还是在使用 JDK7 和 8 . 之前去我 ...

  4. 你还不知道Vue的生命周期吗?带你从Vue源码了解Vue2.x的生命周期(初始化阶段)

    作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...

  5. 什么?作为程序员的你还不知道怎么访问 Google

    今天就一个目的,让你可以FQ成功,其他人我不知道,但就程序员来说,不能使用 Google 那真是一大损失,当然还有对所有人适用的 YouTobu 这个视频网站,资源多的没话说,别的不说,学习英语很方便 ...

  6. 使用过Redis,我竟然还不知道Rdb

    目录 使用过Redis,那就先说说使用过那些场景吧 Rdb文件是什么,它是干什么的 分析工具 小结 联想 推荐阅读 使用过Redis,那就先说说使用过那些场景吧 字符串缓存 //举例 $redis-& ...

  7. 听说你还搞不定java中的==和equals?

    相信很多读者关于==和equals懂了又懵,懵了又懂,如此循环,事实上可能是因为看到的博客文章之类的太多了,长篇大论,加上一段时间的洗礼之后就迷路了.本篇文章再一次理清楚.当然如果觉得本文太啰嗦的话, ...

  8. .NET5都来了,你还不知道怎么部署到linux?最全部署方案,总有一款适合你

    随着2020进入4季度,.NET5正式版也已经与大家见面了.不过,尽管 .NET Core发布已经有四五年的时间,但到目前为止,依旧有很多.NET开发者在坚守者.NET4,原因不尽相同,但最大的问题可 ...

  9. 难道你还不知道Spring之事务的回滚和提交的原理吗,这篇文章带你走进源码级别的解读。

    上一篇文章讲解了获取事务,并通过获取的connection设置只读,隔离级别等:这篇文章讲事务剩下的回滚和提交. 事务的回滚处理 之前已经完成了目标方法运行前的事务准备工作.而这些准备工作的最大目的无 ...

随机推荐

  1. java类加载器是什么?

    类加载器是有了解吗? 解析:底层原理的考察,其中涉及到类加载器的概念,功能以及一些底层的实现. 答:顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说 ...

  2. Java实现 蓝桥杯 历届试题 横向打印二叉树

    问题描述 二叉树可以用于排序.其原理很简单:对于一个排序二叉树添加新节点时,先与根节点比较,若小则交给左子树继续处理,否则交给右子树. 当遇到空子树时,则把该节点放入那个位置. 比如,10 8 5 7 ...

  3. java实现第四届蓝桥杯世纪末星期

    世纪末星期 题目描述 曾有邪教称1999年12月31日是世界末日.当然该谣言已经不攻自破. 还有人称今后的某个世纪末的12月31日,如果是星期一则会- 有趣的是,任何一个世纪末的年份的12月31日都不 ...

  4. 栈 & 队列

    栈 先进者后出,后进者先出,LIFO,典型的"栈"结构 从栈的操作特性上来看,栈是一种"操作受限"的线性表,只允许在一段插入和删除数据. 在功能上来说,数组和链 ...

  5. 用mvc框架查询数据库数据

    介绍下mvc框架,mvc框架一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑. 首先我们 ...

  6. Python简单http服务实现

    1.代码实现 # -*- coding: utf-8 -*-"""Created on Tue Jun 11 18:12:01 2019 @author: wangymd ...

  7. 02-Python基础1

    本节内容 列表.元组操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 1. 列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 ...

  8. Flask 的配置文件

    Flask 的配置文件 四种方法给 Flask 做配置 1直接给 app 对象赋值属性 以字典的形式,给flask做配置 以文件的形式,给flask做配置(django 就是用这种) 以类的形式,给f ...

  9. ecshop头部添加所在城市

    首先,在/includes/lib_main.php中,找到代码:function assign_template($ctype = '', $catlist = array())   ,在方法中添加 ...

  10. @gym - 100591D@ Fox Rocks

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定 N 个点以及 P 条单向道路 Ai -> Bi,每条 ...