CompletableFuture真香,可以替代CountDownLatch!

news2024/11/23 17:06:12

1、背景

之前我们提到了 Future 和 Promise。Future 相当于一个占位符,代表一个操作将来的结果。一般通过 get 可以直接阻塞得到结果,或者让它异步执行然后通过 callback 回调结果。

但如果回调中嵌入了回调呢?如果层次很深,就是回调地狱。

Java 中的 CompletableFuture 其实就是 Promise,用来解决回调地狱问题。Promise 是为了让代码变得优美而存在的。

2、静态方法

从它的源代码中,我们可以看到,CompletableFuture 直接提供了几个便捷的静态方法入口。其中有 run 和 supply 两组。
在这里插入图片描述
run 的参数是 Runnable,而 supply 的参数是 Supplier。前者没有返回值,而后者有,否则没有什么两样。

这两组静态函数,都提供了传入自定义线程池的功能。如果你用的不是外置的线程池,那么它就会使用默认的 ForkJoin 线程池。默认的线程池,大小和用途你是控制不了的,所以还是建议自己传递一个。

典型的代码,写起来是这个样子:

CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
 return "test";
});
String result = future.join();

拿到 CompletableFuture 后,你就可以做更多的花样。

3、其他方法

我们说面说了,CompletableFuture 的主要作用,就是让代码写起来好看。配合 Java8 之后的 Stream 流,可以把整个计算过程抽象成一个流。
前面任务的计算结果,可以直接作为后面任务的输入,就像是管道一样。

thenApply
thenApplyAsync
thenAccept
thenAcceptAsync
thenRun
thenRunAsync
thenCombine
thenCombineAsync
thenCompose
thenComposeAsync

比如,下面代码的执行结果是 99,并不因为是异步就打乱代码执行的顺序了。

CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> 10)
                .thenApplyAsync((e) -> {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    return e * 10;
                }).thenApplyAsync(e -> e - 1);

cf.join();
System.out.println(cf.get());

同样的,函数的作用还要看 then 后面的动词:

  1. apply 有入参和返回值,入参为前置任务的输出
  2. accept 有入参无返回值,会返回 CompletableFuture
  3. run 没有入参也没有返回值,同样会返回 CompletableFuture
  4. combine 形成一个复合的结构,连接两个 CompletableFuture,并将它们的2个输出结果,作为 combine 的输入
  5. compose 将嵌套的 CompletableFuture 平铺开,用来串联两个 CompletableFuture

4、when 和 handle

上面的函数列表,其实还有很多。比如:

whenComplete

when 的意思,就是任务完成时候的回调。比如我们上面的例子,打算在完成任务后,输出一个 done。它也是属于只有入参没有出参的范畴,适合放在最后一步进行观测。

CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> 10)
                .thenApplyAsync((e) -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    return e * 10;
                }).thenApplyAsync(e -> e - 1)
                .whenComplete((r, e)->{
                    System.out.println("done");
                })
                ;

cf.join();
System.out.println(cf.get());

handle 和 exceptionally 的作用,和 whenComplete 是非常像的。
CompletableFuture 的任务是串联的,如果它的其中某一步骤发生了异常,会影响后续代码的运行的。
exceptionally 从名字就可以看出,是专门处理这种情况的。比如,我们强制某个步骤除以 0,发生异常,捕获后返回 -1,它将能够继续运行。

CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> 10)
                .thenApplyAsync(e->e/0)
                .thenApplyAsync(e -> e - 1)
                .exceptionally(ex->{
                    System.out.println(ex);
                    return -1;
                });

cf.join();
System.out.println(cf.get());

handle 更加高级一些,因为它除了一个异常参数,还有一个正常的入参。处理方法也都类似,不再赘述。

当然,CompletableFuture 的函数不仅仅这些,还有更多,根据函数名称很容易能够了解到它的作用。它还可以替换复杂的 CountDownLatch,这要涉及到几个比较难搞的函数。

5、替代 CountDownLatch

考虑下面一个场景。某一个业务接口,需要处理几百个请求,请求之后再把这些结果给汇总起来。

如果顺序执行的话,假设每个接口耗时 100ms,那么 100 个接口,耗时就需要 10 秒。假如我们并行去获取的话,那么效率就会提高。

使用 CountDownLatch 可以解决:

ExecutorService executor = Executors.newFixedThreadPool(5);

CountDownLatch countDown = new CountDownLatch(requests.size());
for(Request request:requests){
    executor.execute(()->{
        try{
        //some opts
        }finally{
            countDown.countDown();
        }
    });
}
countDown.await(200,TimeUnit.MILLISECONDS);

我们使用 CompletableFuture 来替换它:

ExecutorService executor = Executors.newFixedThreadPool(5);

List<CompletableFuture<Result>> futureList = requests
    .stream()
    .map(request->
        CompletableFuture.supplyAsync(e->{
            //some opts
        },executor))
    .collect(Collectors.toList());

CompletableFuture<Void> allCF = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));

allCF.join();

我们这里用到了一个主要的函数,那就是 allOf,用来把所有的 CompletableFuture 组合在一起;类似的还有 anyOf,表示只运行其中一个。

常用的,还有三个函数:

  1. thenAcceptBoth:处理两个任务的情况,有两个任务结果入参,无返回值
  2. thenCombine:处理两个任务的情况,有入参有返回值,最喜欢
  3. runAfterBoth:处理两个任务的情况,无入参,无返回值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/615191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Cloud Alibaba 快速上手搭建公司项目(四)Sentinel

Sentinel是一个开源的云原生流量控制和熔断降级的组件&#xff0c;它可以实现对微服务架构中的服务进行实时监控、自动降级、熔断限流等功能。Sentinel的核心原理是通过滑动窗口的方式对请求进行控制&#xff0c;当请求超过阈值时&#xff0c;会自动触发熔断操作&#xff0c;避…

JavaWeb——JSP实现书城首页

实验名称&#xff1a; JSP实现书城首页 实验目的&#xff1a; &#xff08;1&#xff09;了解JSP的特点及其运行原理 &#xff08;2&#xff09;掌握JSP的基本语法 &#xff08;3&#xff09;熟悉JSP指令和隐式对象的使用 &#xff08;4&#xff09;掌握JSP动作元素的使用…

ABP VNext 利用QQ邮箱发送邮件

ABP VNext 利用QQ邮箱发送邮件 1.配置邮箱1.1 设置1.2账户1.3 开启邮箱服务1.4 生成授权码 2 默认的SmtpEmailSender发送邮件2.1 生成数据库2.2 添加一个后台任务执行发送邮件2.3 服务中注入ISmtpEmailSender2.4 在配置文件添加邮箱信息 3 集成MailKit发送邮件3.1 添加包Volo.A…

从Docker和Kubernetes看Containerd

导读&#xff1a; 在学习Containerd之前&#xff0c;我们需要去了解Docker与Kubernetes这两个使用Containerd最多的技术&#xff0c;也需要明白什么是容器&#xff0c;什么是容器运行时&#xff0c;以及里面涉及的组件&#xff0c;这些组件是用来干什么的&#xff0c;及容器领域…

Zabbix Httpd漏洞修复2

一、背景 漏洞库升级的真是快啊&#xff0c;绿盟扫描的zabbix服务器又有新的漏洞了&#xff0c;经一番折腾&#xff0c;搞定&#xff0c;记录如下。 二、漏洞内容 漏洞主要还是集中在php和http漏洞 三、修复过程 思路还是升级httpd版本解决。 1、查看下zabbix版本&#xff…

【JVM】垃圾回收机制

垃圾回收三大步骤&#xff1a;判断是不是垃圾(垃圾判断算法) -> 标记需要回收的垃圾(三色标记) -> 回收垃圾(垃圾回收算法)。 一、如何判断是不是垃圾&#xff08;垃圾判断算法&#xff09;&#xff1f; &#xff08;1&#xff09;引用计数器算法&#xff1a;对象中记录…

基于STM32F103C8T6的超声波测距——串口输出

文章目录 前言一、超声波模块介绍1、产品特点2、超声波模块的时序图 二、STM32CubeMx创建工程1、配置项目2、keil代码设置3、效果 三、总结四、参考资料 前言 环境&#xff1a; 1、硬件&#xff1a;stm32f103c8t6 核心板 2、软件&#xff1a;STM32CubeMX 6.4.0 3、软件&#xf…

进程通信管道制作

利用父子进程 创建管道利用pipe函数 // 1.创建管道int pipefd[2] {0}; //[0] 读端 &#xff0c;[1]写端int n pipe(pipefd);assert(n ! -1); // debug 在release下会裁减(void)n;//防止在release下报错cout << "fd[0]:" << pipefd[0] << endl…

多用户商城开源-多店铺商城系统平台开发

多用户商城开源是指一种基于开放源代码的电子商务平台&#xff0c;允许多个用户共享一个平台&#xff0c;每位用户可以创建自己的电子商城&#xff0c;并在平台上进行交易、管理、营销等操作。多用户商城开源通常包含多种功能&#xff0c;如商品管理、订单管理、支付集成、促销…

vue diff算法与虚拟dom知识整理(14) patchVNode处理子节点新增和删减

上文 vue diff算法与虚拟dom知识整理(13) 手写patch子节点更新换位策略 我们实现了子节点位置的更新策略 但还有一些匹配不到的情况会导致死循环 那么我们继续来优化一下 我们先将src下的 index.js 代码改成这样 import h from "./snabbdom/h"; import patch from …

Scrapy ImagesPipeline下载图片

一、 ImagesPipeline是啥 ImagesPipeline是scrapy自带的类&#xff0c;用来处理图片(爬取时将图片下载到本地)。 二、ImagesPipeline优势&#xff1a; 将下载图片转换成通用的jpg和rgb格式避免重复下载缩略图生成图片大小过滤异步下载 三、ImagesPipeline工作流程 爬取一个…

Echarts构建指定省份的地图

1. 自行准备好Echarts环境 Echarts官网&#xff1a;https://echarts.apache.org/zh/index.html 2. 下载需要的省份或者城市的json地理信息文件 下载我们需要显示地区的Json数据&#xff0c;这个Json数据用于Echart的地图显示 例如我这里是下载的&#xff1a;湖南、湖北、四川…

同步模式之顺序控制线程执行

tip: 作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 文章目录 一、同步模式之顺序控制线程执行二、代码样例三、三个线程分别输出a、b、c&#xff0c;按顺序输…

作为一个项目管理成员,怎样才能有效监控项目执行?

作为一个项目管理成员&#xff0c;有效监控项目执行是确保项目成功的关键。在项目执行期间&#xff0c;您需要密切关注项目进展&#xff0c;并及时采取行动来纠正任何偏差。以下是几个有效的方法&#xff0c;可以帮助您监控项目执行并确保项目按计划顺利进行。 1. 制定详细的项…

C语言进阶--数据的存储

目录 数据类型介绍 基本内置类型&#xff1a; 类型的意义&#xff1a; 类型的基本归纳&#xff1a; 整型在内存中的存储 原码&#xff0c;反码和补码&#xff1a; 大小端存储模式&#xff1a; 大小端产生原因&#xff1a; 浮点型在内存中的存储 数据类型介绍 基本内…

六、机械手的种类

机械手是机器人能够完成指令的一个重要输出装置&#xff0c;机器臂是否合理、有效&#xff0c;决定了机 器人能否发挥出应有的作用。 机械手是一种能模仿人手和臂的某些动作功能&#xff0c;用以按固定程序抓取、搬运物件或操作工具的自动操作装置。特点是可以通过编程来完成各…

wy的leetcode刷题记录_Day68

wy的leetcode刷题记录_Day68 声明 本文章的所有题目信息都来源于leetcode 如有侵权请联系我删掉! 时间&#xff1a;2023-6-6 前言 目录 wy的leetcode刷题记录_Day68声明前言1019. 链表中的下一个更大节点题目介绍思路代码收获 1019. 链表中的下一个更大节点 2352. 相等行列…

CPU、内存、缓存的关系

术语解释 &#xff08;1&#xff09;CPU&#xff08;Central Processing Unit&#xff09; 中央处理器 &#xff08;2&#xff09;内存 内存用于暂时存放CPU中的运算数据&#xff0c;以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁&#xff0c;内存的运行决定…

Docker容器管理

docker容器相当于一个进程&#xff0c;性能接近于原生&#xff0c;几乎没有损耗&#xff1b; docker容器在单台主机上支持的数量成百上千&#xff1b; 容器与容器之间相互隔离&#xff1b; 镜像是创建容器的基础&#xff0c;可以理解镜像为一个压缩包 docker容器的管理 容…

深耕电力行业,百度智能云助力电厂节煤降耗

山西省吕梁市汾阳市三泉镇&#xff0c;晋能集团旗下山西国峰煤电有限责任公司的两台300MW循环流化床直接空冷机组正在运行&#xff0c;燃煤通过传送带进入锅炉燃烧&#xff0c;将水加热成高温高压蒸汽&#xff0c;用以推动汽轮机拖动发电机旋转发电&#xff0c;支撑工业生产、点…