CompletableFuture总结和实践
CompletableFuture被设计在Java中进行异步编程。异步编程意味着在主线程之外创建一个独立的线程,与主线程分隔开,并在上面运行一个非阻塞的任务,然后通知主线程进展,成功或者失败。
一、概述
1.CompletableFuture和Future的区别?
CompletableFuture和Future出现的原因是继承Thread或者实现Runnable接口的异步线程没有返回值,需要返回值的异步线程可以通过CompletableFuture和Future来创建。
CompletableFuture和Future都可以获取到异步线程的返回值,但是Future只能通过get()方法阻塞式获取,CompletableFuture由于实现了CompletionStage接口,可以通过丰富的异步回调方式来执行后续的操作,同时还能对多个任务按照先后顺序进行任务编排。
2.特性
CompletableFuture的作用主要体现在:(1)异步回调;(2)任务编排;
3.使用场景
- 执行比较耗时的操作时,尤其是那些依赖一个或多个远程服务的操作,使用异步任务可以改善程序的性能,加快程序的响应速度
- 使用CompletableFuture类,它提供了异常管理的机制,让你有机会抛出、管理异步任务执行种发生的异常
- 如果这些异步任务之间相互独立,或者他们之间的的某一些的结果是另一些的输入,你可以讲这些异步任务构造或合并成一个
二、原理
CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。

Future接口主要提供get()和join()方法,可以获取任务的返回值,同时也会阻塞调用线程。
CompletionStage接口提供了丰富的执行异步调用和任务处理的接口,任务之间可以分为存在时序关系,包括串行关系、并行关系和汇聚关系等。
三、实践
1.创建任务和获取结果
(1)runAsync创建任务
通过runAsync()方法创建的异步任务没有返回值,其中有有runAsync(Runnable runnable)和runAsync(Runnable runnable, Executor executor)两个重载方法,后者比前者多一个Executor executor,即可以传入自定义的线程池,如果没传即用默认线程池(ForkJoinPool.commonPool())。
ExecutorService executor = Executors.newFixedThreadPool(3);CompletableFuture<Void> f1 = CompletableFuture.runAsync(new Runnable() {@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("f1 start");}
}, executor);
(2)supplyAsync创建任务
通过supplyAsync()方法创建的异步任务有返回值,其中有有supplyAsync(Runnable runnable)和supplyAsync(Runnable runnable, Executor executor)两个重载方法,后者比前者多一个Executor executor,即可以传入自定义的线程池,如果没传即用默认线程池(ForkJoinPool.commonPool())。
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(new Supplier<String>() {@Overridepublic String get() {System.out.println("f2 start");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}return "f2 return";}
}, executor);
总结:
runAsync创建的异步任务没有返回值,supplyAsync创建的异步任务有返回值。
(3)获取结果和结束任务
// 如果完成则返回结果,否则就抛出具体的异常
public T get()
// 最大时间等待返回结果,否则就抛出具体异常
public T get(long timeout, TimeUnit unit)
// 如果完成则返回结果值(或抛出任何遇到的异常),否则返回默认值。
public T getNow(T valueIfAbsent)
// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T join()
//如果任务还未完成,直接给他返回值置为value
public boolean complete(T value)
2.异步回调
(4)whenComplete和whenCompleteAsync
whenComplete()方法是当某个任务执行完成后执行的回调方法。该方法会将该任务的执行结果或者执行期间抛出的异常传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和是该任务的返回值,如果该任务正常执行,则get方法返回执行结果,如果是执行异常,则get方法抛出异常。该任务抛出的异常,一般用exceptionally()处理,其入参是异常Throwable throwable,可以有返回值。
whenComplete和whenCompleteAsync和区别在于whenComplete是在当前线程中执行该回调任务,whenCompleteAsync是会另启动一个线程来执行该回调任务,默认情况下是使用ForkJoinPool.commonPool()线程池中的线程,也可以设置自定义线程池。后面以-Async为后缀的方法也都是这样的区别。
CompletableFuture<String> f7 = f2.whenCompleteAsync((result, e) -> {System.out.println("f7 start");if (1 == 1) {throw new IllegalStateException("f7 IllegalStateException");}}
).exceptionally(new Function<Throwable, String>() {@Overridepublic String apply(Throwable throwable) {System.out.println("f8 exception: " + throwable);return "f8 return";}
});
(5)handle和handleAsync
handle方法也是当某个任务执行完成后执行的回调方法,整体功能和whenComplete方法差不多,但是handleAsync方法会有返回值,handleAsync()可以传入自定义线程池。
CompletableFuture<String> f5 = f2.handleAsync(new BiFunction<String, Throwable, String>() {@Overridepublic String apply(String s, Throwable throwable) {System.out.println("f5 start");if (1 == 1) {throw new IllegalStateException("f5 IllegalStateException");}return "f5 return";}}, executor).exceptionally(new Function<Throwable, String>() {@Overridepublic String apply(Throwable throwable) {System.out.println("f6 exception: " + throwable);return "f6 return";}
});
总结:
whenComplete和handle的区别?
whenComplete的回调方法没有返回值,handle方法有返回值?
(6)thenApply和thenApplyAsync
thenApply 表示某个任务执行完成后执行回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,带有返回值。其中
CompletableFuture<String> f3 = f2.thenApplyAsync(new Function<String, String>() {@Overridepublic String apply(String s) {System.out.println(s + ",f3 start");return "f3 return";}
}, executor);
(7)thenAccept和thenAcceptAsync
thenAccep表示某个任务执行完成后执行的回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,无返回值。
CompletableFuture<Void> f3 = f2.thenAcceptAsync(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println("f3 start");}
}, executor);
(8)thenRun和thenRunAsync
thenRun表示某个任务执行完成后执行的动作,即回调方法,无入参,无返回值。
CompletableFuture<Void> f4 = f2.thenRunAsync(new Runnable() {@Overridepublic void run() {System.out.println("f4 start");}
}, executor);
异步回调方法总结:
| 方法 | 入参 | 返回值 | 异常处理 |
|---|---|---|---|
| whenComplete | 有入参 | 无返回值 | 能抛出异常 |
| handle | 有入参 | 有返回值 | 能抛出异常 |
| thenApply | 有入参 | 有返回值 | 不能抛出异常 |
| thenAccept | 有入参 | 无返回值 | 不能抛出异常 |
| thenRun | 无入参 | 无返回值 | 不能抛出异常 |
3.任务编排
(9)thenCombine、thenAcceptBoth 和runAfterBoth
这三个方法都是将两个CompletableFuture组合起来处理,只有两个任务都正常完成时,才进行下阶段任务。可以理解为为:两个任务AND汇聚后才能执行。
区别:thenCombine会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
ExecutorService executor = Executors.newFixedThreadPool(3);CompletableFuture<String> f1 = CompletableFuture.supplyAsync(new Supplier<String>() {@Overridepublic String get() {System.out.println("f1 running");try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}return "f1 return";}}, executor);CompletableFuture<String> f2 = CompletableFuture.supplyAsync(new Supplier<String>() {@Overridepublic String get() {System.out.println("f2 running");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}return "f2 return";}}, executor);CompletableFuture<String> f30 = CompletableFuture.supplyAsync(new Supplier<String>() {@Overridepublic String get() {System.out.println("f30 running");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return "f30 return";}}, executor);CompletableFuture<String> f3 = f1.thenCombine(f2, new BiFunction<String, String, String>() {@Overridepublic String apply(String s, String s2) {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s + "," + s2 + "," + "f3 running");return "f3 ruturn";}});CompletableFuture<Void> f4 = f1.thenAcceptBoth(f2, new BiConsumer<String, String>() {@Overridepublic void accept(String s, String s2) {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s + "," + s2 + "," + "f4 running");}});
(10)applyToEither、acceptEither和runAfterEither
这三个方法和上面一样也是将两个CompletableFuture组合起来处理,当有一个任务正常完成时,就会进行下阶段任务。可以理解为:两个任务OR汇聚后才能执行。
区别:applyToEither会将已经完成任务的执行结果作为所提供函数的参数,且该方法有返回值;acceptEither同样将已经完成任务的执行结果作为方法入参,但是无返回值;runAfterEither没有入参,也没有返回值。
CompletableFuture<String> f6 = f1.applyToEither(f2, new Function<String, String>() {@Overridepublic String apply(String s) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s + "," + "f6 running");return "return f6";}});CompletableFuture<Void> f7 = f1.acceptEither(f2, new Consumer<String>() {@Overridepublic void accept(String s) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(s + "," + "f7 running");}
});CompletableFuture<Void> f8 = f1.runAfterEither(f2, new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("f7 running");}
});
(11)allOf / anyOf
allOf和anyOf都是CompletableFuture的方法,他们针对的都是多任务汇聚后才能执行的逻辑,可以理解为多任务AND/OR汇聚后模式。
allOf:CompletableFuture是多个任务都执行完成后才会执行,只有有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。
anyOf :CompletableFuture是多个任务只要有一个任务执行完成,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回执行完成任务的结果。
CompletableFuture<Void> f9 = CompletableFuture.allOf(f1, f2, f30);
CompletableFuture<Object> f10 = CompletableFuture.anyOf(f1, f2, f30);
任务编排方法总结:
| 类型 | 方法 | 入参 | 返回值 | 描述 |
|---|---|---|---|---|
| 两个任务AND汇聚 | thenCombine | 有入参 | 有返回值 | |
| 两个任务AND汇聚 | thenAcceptBoth | 有入数 | 无返回值 | |
| 两个任务AND汇聚 | runAfterBoth | 无入参 | 无返回值 | |
| 两个任务OR汇聚 | applyToEither | 有入参 | 有返回值 | |
| 两个任务OR汇聚 | acceptEither | 有入参 | 无返回值 | |
| 两个任务OR汇聚 | runAfterEither | 无入参 | 无返回值 | |
| 多任务AND汇聚后模式 | allOf | 全部执行 | ||
| 多任务OR汇聚后模式 | anyOf | 至少一个执行 |
总结以上方法:
- 以Async结尾的方法,都是异步方法,对应的没有Async则是同步方法,一般都是一个异步方法对应一个同步方法;以Async后缀结尾的方法,都有两个重载的方法,一个是使用内容的forkjoin线程池,一种是使用自定义线程池;
- 以Apply开头或者结尾的方法,入口有参数,有返回值;
- 以supply开头的方法,入口也是没有参数的,但是有返回值;
- 以Accept开头或者结尾的方法,入口参数是有参数,但是没有返回值;
- 以run开头的方法,其入口参数一定是无参的,并且没有返回值,类似于执行Runnable方法。
- 和异步方法相比,任务编排方法多数带有-Both或-Either的后缀,-Both表示需要执行全部任务,-Either表示至少执行一个任务。
4.代码实现
参考资料
CompletableFuture使用详解(全网看这一篇就行):https://blog.csdn.net/zsx_xiaoxin/article/details/123898171 (主要参考)
CompletableFuture使用大全,简单易懂:https://juejin.cn/post/6844904195162636295
CompletableFuture用法详解:https://zhuanlan.zhihu.com/p/344431341
并发编程系列-CompletableFuture:https://zhuanlan.zhihu.com/p/650700731
Java CompletableFuture实现多线程异步编排:https://juejin.cn/post/7140244126679138312
使用CompletableFuture:https://www.liaoxuefeng.com/wiki/1252599548343744/1306581182447650
并发编程 - CompletableFuture 解析 | 京东物流技术团队:https://zhuanlan.zhihu.com/p/646472720
本文由博客一文多发平台 OpenWrite 发布!
相关文章:
CompletableFuture总结和实践
CompletableFuture被设计在Java中进行异步编程。异步编程意味着在主线程之外创建一个独立的线程,与主线程分隔开,并在上面运行一个非阻塞的任务,然后通知主线程进展,成功或者失败。 一、概述 1.CompletableFuture和Future的区别&…...
使用Nginx调用网关,然后网关调用其他微服务
问题前提:目前我的项目是已经搭建了网关根据访问路径路由到微服务,然后现在我使用了Nginx将静态资源都放在了Nginx中,然后我后端定义了一个接口访问一个html页面,但是html页面要用到静态资源,这个静态资源在我的后端是…...
windows搭建WebDAV服务,并内网穿透公网访问【无公网IP】
windows搭建WebDAV服务,并内网穿透公网访问【无公网IP】 文章目录 windows搭建WebDAV服务,并内网穿透公网访问【无公网IP】1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访…...
PAT 1097 Deduplication on a Linked List
个人学习记录,代码难免不尽人意 Given a singly linked list L with integer keys, you are supposed to remove the nodes with duplicated absolute values of the keys. That is, for each value K, only the first node of which the value or absolute value o…...
Flink 数据集成服务在小红书的降本增效实践
摘要:本文整理自实时引擎研发工程师袁奎,在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分: 小红书实时服务降本增效背景Flink 与在离线混部实践实践过程中遇到的问题及解决方案未来展望 点击查看原文视频 & 演…...
jellyfin使用ipv6+DDNS实现外网访问
前言 原本使用frp的方案进行外网访问jellyfin,但是阿里云的轻量服务器的带宽只有5M,只能支持看1080p的视频,看4K有点吃力,为了有更好的观影体验,选择ipv6DDNS的方式实现外网访问,此方案能跑满群晖的上行带宽…...
Codeforces EDU 151 Div.2
文章目录 A. Forbidden IntegerB. Come TogetherC. Strong PasswordD. Rating SystemE. Boxes and Balls A. Forbidden Integer Problem - A - Codeforces 给定整数n,从1~k中选择除了x的数,使这些数之和为n,每个数可以选择无限次 爆搜&…...
V2board缓存投毒漏洞复现
1.什么是缓存投毒 缓存投毒(Cache poisoning),通常也称为域名系统投毒(domain name system poisoning),或DNS缓存投毒(DNS cache poisoning)。它是利用虚假Internet地址替换掉域名系…...
2023面试八股文 ——Java基础知识
Java基础知识 一.Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些特点什么是字节码?采用字节码的大好处是什么什么是Java程序的主类?应用程序和小程序的主类有何不同?…...
在linux系统中修改mysql数据目录
目录 1.查看mysql默认存储路径2.停止mysql服务3.移动或复制原数据目录4.修改配置文件5.修改启动文件6.配置AppArmor访问控制规则7.重启apparmor服务8.启动mysql 1.查看mysql默认存储路径 在/etc/mysql/mysql.conf.d/mysqld.cnf中的datadir配置项。 datadir /var/lib/mysql2…...
ORB-SLAM2学习笔记9之图像帧Frame
先占坑,明天再完善… 文章目录 0 引言1 Frame类1.1 成员函数1.2 成员变量 2 Frame类的用途 0 引言 ORB-SLAM2学习笔记8详细了解了图像特征点提取和描述子的生成,本文在此基础上,继续学习ORB-SLAM2中的图像帧,也就是Frame类&#…...
面试热题(不同的二分搜索树)
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 经典的面试题,这部分涉及了组合数学中的卡特兰数,如果对其不清楚的同学可以去看我以前的博客卡特兰数 …...
MybatisPlus整合p6spy组件SQL分析
目录 p6spy java为什么需要 如何使用 其他配置 p6spy p6spy是一个开源项目,通常使用它来跟踪数据库操作,查看程序运行过程中执行的sql语句。 p6spy将应用的数据源给劫持了,应用操作数据库其实在调用p6spy的数据源,p6spy劫持到…...
项目实战 — 博客系统③ {功能实现}
目录 一、编写注册功能 🍅 1、使用ajax构造请求(前端) 🍅 2、统一处理 🎄 统一对象处理 🎄 保底统一返回处理 🎄 统一异常处理 🍅 3、处理请求 二、编写登录功能 🍅 …...
卷积神经网络全解:(AlexNet/VGG/ GoogLeNet/LeNet/ResNet/卷积/激活/池化/全连接)、现代卷积神经网络、经典卷积神经网络
CNN,卷积神经网络,Convolution Neural Network 卷积计算公式:N (W-F2p)/s1 这个公式每次都得看看,不能忘 1 经典网络 按照时间顺序 1.1 LeNet LeNet是 Yann LeCun在1998年提出,用于解决手…...
WDM 模型(Windows Driver Model)简述
WDM 模型(Windows Driver Model) 是微软公司为 Windows98 和 Windows2000 的驱动程序设计的一种架构,在 WDM 驱动程序模型中,每个硬件设备 至少有两个驱动程序。其中一个为功能驱动程序,它了解硬件工作的所有细节,负 责初始化 …...
【算法刷题之数组篇(1)】
目录 1.leetcode-59. 螺旋矩阵 II(题2.题3相当于二分变形)2.leetcode-33. 搜索旋转排序数组3.leetcode-81. 搜索旋转排序数组 II(与题目2对比理解)(题4和题5都是排序双指针)4.leetcode-15. 三数之和5.leetcode-18. 四数之和6.leet…...
【数据挖掘】使用 Python 分析公共数据【01/10】
一、说明 本文讨论了如何使用 Python 使用 Pandas 库分析官方 COVID-19 病例数据。您将看到如何从实际数据集中收集见解,发现乍一看可能不那么明显的信息。特别是,本文中提供的示例说明了如何获取有关疾病在不同国家/地区传播速度的信息。 二、准备您的…...
html怎么插入视频?视频如何插入页面
html怎么插入视频?视频如何插入页面 HTML 的功能强大,基本所有的静态效果都可以在此轻松呈现,各种视频网站内有大量的视频内容,本篇文章教你如何在 html 中插入视频 代码如下: <!DOCTYPE html> <html> …...
游戏服务端性能测试
导语:近期经历了一系列的性能测试,涵盖了Web服务器和游戏服务器的领域。在这篇文章中,我将会对游戏服务端所做的测试进行详细整理和记录。需要注意的是,本文着重于记录,而并非深入的编程讨论。在这里,我将与…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...
