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服务器和游戏服务器的领域。在这篇文章中,我将会对游戏服务端所做的测试进行详细整理和记录。需要注意的是,本文着重于记录,而并非深入的编程讨论。在这里,我将与…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
