通俗易懂讲 CompletableFuture

使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

构造任务

通俗易懂讲 CompletableFuture

我们通常采用静态方法来构造任务,接下来演示几种常见的构造任务的方式。

supplyAsync

supplyAsync() 方法的参数是一个 Supplier 函数式接口

@FunctionalInterface
public interface Supplier {

    /**
     * Gets a result.

     *
     * @return a result
     */
    T get();
}

Supplier 接口不接收参数,但是返回一个泛型对象。因此,supplyAsync() 方法需要传入一个实现了 Supplier 接口的对象,我们这里采用函数式编程的方式构造一个 Supplier 实现类。

public static void demo1() {
    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("supply");
        return "supply";
    });
}

supplyAsync() 一共有两个方法,另一个方法是接收一个 Executor 线程池,如果没传入这个线程池,那么默认采用 ForkJoinPool 线程池

public static void demo1() {
    Executor executor = Executors.newCachedThreadPool();
    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("supply");
        return "supply";
    }, executor);
}

runAsync

runAsync() 方法的参数是一个 Runnable函数式接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable 接口不接收参数,也没有返回值。因此,runAsync() 方法需要传入一个实现了 Runnable 接口的对象,我们这里采用函数式编程的方式构造一个 Runnable 实现类。

如下:runAsync() 方法也有两个重载方法,一个传入 Runnable 实现类,一个传入Runnable 实现类以及 Executor 线程池。

public static void demo2() {
    CompletableFuture future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    Executor executor = Executors.newCachedThreadPool();
    CompletableFuture future1 = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, executor);
}

completedFuture

completedFuture() 方法接收一个 Object 对象。

老实说,感觉这个方法没啥用。

public static void demo3() throws ExecutionException, InterruptedException {
    CompletableFuture future = CompletableFuture.completedFuture(1);
    System.out.println(future.get());
}

anyOf

anyOf() 方法接收 N 个 CompletableFuture 任务,他的参数是一个可变长的参数。

代表着接收任意一个对象的返回。

如下,first 和 second 谁先返回就取谁。这里常用的常见是从多方数据源取数据,不管谁先返回结果,我拿着结果就继续干下面的事

public static void demo4() throws ExecutionException, InterruptedException {
    CompletableFuture first = CompletableFuture.supplyAsync(() -> "first");
    CompletableFuture second = CompletableFuture.supplyAsync(() -> "second");
    CompletableFuture anyFuture = CompletableFuture.anyOf(first, second);
    System.out.println(anyFuture.get());
}

allOf

allOf() 方法接收 N 个 CompletableFuture 任务,他的参数是一个可变长的参数。

该方法代表需要所有任务都执行完毕,我才能接着干下面的事情。这里可以看到, allOf 构造出来的任务 allFuture 是一个 void 返回值的,代表着可以拿这个对象做接下来的事情,这里我们在后面会再介绍到。

public static void demo5() {
    CompletableFuture first = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("first");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "first";
    });
    CompletableFuture second = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("second");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "second";
    });
    CompletableFuture allFuture = CompletableFuture.allOf(first, second);
    allFuture.join();
}

链式执行

构造完对象后就是到了执行的阶段了。其实上面的例子中,当我们构造一个任务的时候这个任务已经开始执行了,我们拿到的知识他的回调对象,通过这个回调对象我们可以接下来做一些操作。

这里先拿一个简单的列子做下演示:

先查找 id,然后根据 id 查询姓名,再根据姓名查询职位,最后打印,这边可以看到,一共构造了三个任务:查id、查姓名、查职位。通过很简便的 . 语法异步执行完了。

public static void demo6() throws ExecutionException, InterruptedException {
    String position = CompletableFuture.supplyAsync(Main::findId)
        .thenApplyAsync(Main::findNameById)
        .thenApplyAsync(Main::findPositionByName).get();
    System.out.println(position);
}

public static Integer findId() {
    return 1;
}

public static String findNameById(Integer id) {
    return "小王";
}

public static String findPositionByName(String name){
    return "CEO";
}

输出:

CEO

通俗易懂讲 CompletableFuture

可以看到,实例方法有很多,大多数通过名字就可以知道是干什么的,具体用法可以查阅 jdk api。这里调几个简单的来介绍介绍。

  • xxx():表示该方法将继续在已有的线程中执行;
  • xxxAsync():表示将异步在线程池中执行。

thenApplyAsync

通俗易懂讲 CompletableFuture

这里可以看到 thenApplyAsync 有两个构造函数一个接收线程池,一个不接受。但是必须需要有一个实现了 Function 函数式接口的类才行,上面的例子采用了函数式编程,Main::findId 这种语法。

exceptionally

通俗易懂讲 CompletableFuture

接收一个 Function 函数式接口,该接口的入参必须是 Throwable 对象,因此可以打印出堆栈信息。

public static void demo6() throws ExecutionException, InterruptedException {
    CompletableFuture.supplyAsync(Main::findId).exceptionally(e -> {
        e.printStackTrace();
        return null;
    });
}

其他的方法使用差不多,有兴趣的可以查阅 jdk api

总结

最近业务上有个详情页,一个 rpc 调完填充信息,再调另一个再填充信息,最后返回给前端。总感觉有优化空间,然后发现了响应式编程,java 8 的 CompletableFuture 也是响应式编程的一种,还在调研其他的如 RxJava 等,在这里先学习下最简单的 CompletableFuture 。

ps

文章为本人学习过程中的一些个人见解,漏洞是必不可少的,希望各位大佬多多指教,帮忙修复修复漏洞!!!

Original: https://www.cnblogs.com/cyrus-s/p/15485182.html
Author: 三木同学
Title: 通俗易懂讲 CompletableFuture

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/715177/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Linux虚拟机上按安装jdk1.8.0

    Linux虚拟机上按安装jdk1.8.0 1.准备工作 jdk1.8.0下载地址: http://www.oracle.com/technetwork/java/javase/do…

    技术杂谈 2023年7月11日
    059
  • Linux 系统安装RocketMQ

    准备工作 1.去官网下载一个安装包 1.解压 unzip rocketmq-all-4.9.0-bin-release.zip -d /download/compress/ 2.进…

    技术杂谈 2023年7月11日
    071
  • 前端工作流规范

    使用GitFlow, 在项目中会存在两个长期分支,主分支(master) 和 开发分支(develop)。 主分支(master): 该主分支代码用于对外发布的代码(一般指线上已经…

    技术杂谈 2023年6月1日
    083
  • APACHE快速安装流程梳理

    快速安装开始: 【环境配置1】 yum -y install gcc gcc-c++ wget 保留操作(可跳过): yum -y removeapr-util-devel apr…

    技术杂谈 2023年7月10日
    047
  • 为Jupyter notebook创建新kernel

    在新的虚拟环境中创建kernel 进入需要创建kernel的虚拟环境 conda activate pytorch 安装ipykernel ipykernel是必须安装的,也可以直…

    技术杂谈 2023年7月25日
    094
  • vue系列—snabbdom.js使用及源码分析(九)

    一:什么是snabbdom? 在学习Vue或React中,我们了解最多的就是虚拟DOM,虚拟DOM可以看作是一颗模拟了DOM的Javascript树,主要是通过vnode实现一个无…

    技术杂谈 2023年6月1日
    076
  • WaterfallTree(瀑布树) 详细技术分析系列

    前言 WaterfallTree(瀑布树) 是最强纯C#开源NoSQL和虚拟文件系统-STSdb专有的(版权所有/专利)算法/存储结构。 参考 关于STSdb,我之前写过几篇文章,…

    技术杂谈 2023年5月31日
    085
  • 了解Unicode编码

    一. Unicode是什么? Unicode是一种字符编码方案,它为每种语言中的每个字符都设定了统一唯一的二进制编码。以实现跨语言、跨平台进行文本转换。 Unicode是为了解决传…

    技术杂谈 2023年6月1日
    087
  • 为BlueLake主题增加图片放大效果

    fancyBox 是一个流行的媒体展示增强组件,可以方便为网站添加图片放大、相册浏览、视频弹出层播放等效果。优点有使用简单,支持高度自定义,兼顾触屏、响应式移动端特性,总之使用体验…

    技术杂谈 2023年7月25日
    062
  • 为什么列式存储会被广泛用在 OLAP 中?

    大家好,我是大D。 不知是否有小伙伴们疑问,为什么列式存储会广泛地应用在 OLAP 领域,和行式存储相比,它的优势在哪里?今天我们一起来对比下这两种存储方式的差别。 其实,列式存储…

    技术杂谈 2023年7月25日
    092
  • 阿尔兹海默症_记不住的java8 List操作

    一丢丢废话 使用java8对list操作不是很方便嘛但是呢,脑子不好,似乎老是记不住 取出List中的某一列作为一个新的list 假设有一个实例User,User中有成员变量nam…

    技术杂谈 2023年7月25日
    052
  • 视图是否有主键的问题

    试图中是没有主键,也不能建立主键,可以在试图中建立索引,称之为索引视图,这样就物理化了试图中的数据创建视图中的第一个索引必须是唯一聚集索引,建立聚集索引之后你就可以建立其它非聚集索…

    技术杂谈 2023年5月31日
    086
  • 框架篇(二)Spring面试题(一)

    Spring面试题 Spring常见面试题总结(超详细回答)_张维鹏的博客-CSDN博客_spring面试题一个不错的总结!!! 1. 你是怎样理解Spring的 我和面试官的一个…

    技术杂谈 2023年7月11日
    072
  • 彩食鲜架构团队风采

    2020-4-15 架构组支援福州做冷热数据拆分 4-24 听说最近测试环境不稳定,首席掏腰包请大家喝个茶吧~~ ps: 服务器资源不足,咋办? :) 4-25 诸事不顺,上天台聊…

    技术杂谈 2023年7月23日
    056
  • MySQL根据指定字段值判断,给字段拼接指定字符

    如上,主要用到两个SQL函数 1、字符串拼接函数: CONCAT(str1,str2,…) 2、CASE WHEN判断函数: CASE sex WHEN ‘1’ THEN ‘男…

    技术杂谈 2023年7月11日
    066
  • Spark学习(2)RDD编程

    RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、弹性、里面的元素可并行计算的集合 R…

    技术杂谈 2023年7月24日
    055
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球