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实现第六届蓝桥杯牌型整数

    牌型整数 题目描述 小明被劫持到X赌城,被迫与其他3人玩牌. 一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张. 这时,小明脑子里突然冒出一个问题: 如果不考虑花色,只考虑点数,也不 ...

  2. Python--文件操作(操作文件)

    文件的操作包含:读.写.修改 文件的多种操作: # 读取文件的所有内容 data = open("yesteday.txt", encoding="utf-8" ...

  3. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

  4. RabbitMQ系列之【启动过程中遇到问题及解决方案】

    1.如果显示找不到主机,请在hosts文件中添加: vi /etc/hosts 127.0.0.1 localhost 2.从3.3.1版本开始,RabbitMQ默认不允许远程ip登录,即只能使用lo ...

  5. Python的数据的基本类型

    基本数据类型 int整数 str 字符串   一般不存放大量的数据 bool  布尔值,用来判断. True,False    list  列表.存放大量数据,[]表示,里面可以放各种数据类型     ...

  6. Ubuntu:E: Sub-process /usr/bin/dpkg returned an error code (1)

    Ubuntu系统安装软件时报以下错误: E: Sub-process /usr/bin/dpkg returned an error code (1) 解决: mv /var/lib/dpkg/inf ...

  7. delete语句的基本用法

    DELETE FROM tb_courses WHERE course_id=;

  8. SpringMVC框架搭建流程(完整详细版)

    SpringMVC框架搭建流程 开发过程 1)配置DispatcherServlet前端控制器 2)开发处理具体业务逻辑的Handler(@Controller. @RequestMapping) 3 ...

  9. Android学习笔记.9.png格式图片

    .9.png可以保证图片在合适的位置进行局部拉伸,避免了图片全局缩放造成的图片变形问题.AS提供了制作点9图片的便捷入口,并且会检查你的.9图是否有不合理的拉伸区域. 选中图片点击create 9-p ...

  10. show and hide. xp扩展名

    reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v HideFileExt ...