编程老司机带你玩转 CompletableFuture 异步编程
本文从实例出发,介绍 CompletableFuture
基本用法。不过讲的再多,不如亲自上手练习一下。所以建议各位小伙伴看完,上机练习一把,快速掌握 CompletableFuture
。
个人博文地址:https://sourl.cn/s5MbCm
全文摘要:
Future
VSCompletableFuture
CompletableFuture
基本用法
0x00. 前言
一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。 Java 提供 Runnable
Future<V>
两个接口用来实现异步任务逻辑。
虽然 Future<V>
可以获取任务执行结果,但是获取方式十方不变。我们不得不使用Future#get
阻塞调用线程,或者使用轮询方式判断 Future#isDone
任务是否结束,再获取结果。
这两种处理方式都不是很优雅,JDK8 之前并发类库没有提供相关的异步回调实现方式。没办法,我们只好借助第三方类库,如 Guava
,扩展 Future
,增加支持回调功能。相关代码如下:
虽然这种方式增强了 Java 异步编程能力,但是还是无法解决多个异步任务需要相互依赖的场景。
举一个生活上的例子,假如我们需要出去旅游,需要完成三个任务:
- 任务一:订购航班
- 任务二:订购酒店
- 任务三:订购租车服务
很显然任务一和任务二没有相关性,可以单独执行。但是任务三必须等待任务一与任务二结束之后,才能订购租车服务。
为了使任务三时执行时能获取到任务一与任务二执行结果,我们还需要借助 CountDownLatch
。
0x01. CompletableFuture
JDK8 之后,Java 新增一个功能十分强大的类:CompletableFuture
。单独使用这个类就可以轻松的完成上面的需求:
大家可以先不用管
CompletableFuture
相关API
,下面将会具体讲解。
对比 Future<V>
,CompletableFuture
优点在于:
- 不需要手工分配线程,JDK 自动分配
- 代码语义清晰,异步任务链式调用
- 支持编排异步任务
怎么样,是不是功能很强大?接下来抓稳了,小黑哥要发车了。
1.1 方法一览
首先来通过 IDE 查看下这个类提供的方法:
稍微数一下,这个类总共有 50 多个方法,我的天。。。
不过也不要怕,小黑哥帮你们归纳好了,跟着小黑哥的节奏,带你们掌握 CompletableFuture
。
若图片不清晰,可以关注『程序通事』,回复:『233』,获取该思维导图
1.2 创建 CompletableFuture 实例
创建 CompletableFuture
对象实例我们可以使用如下几个方法:
第一个方法创建一个具有默认结果的 CompletableFuture
,这个没啥好讲。我们重点讲述下下面四个异步方法。
前两个方法 runAsync
不支持返回值,而 supplyAsync
可以支持返回结果。
这个两个方法默认将会使用公共的 ForkJoinPool
线程池执行,这个线程池默认线程数是 CPU 的核数。
可以设置 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数
使用共享线程池将会有个弊端,一旦有任务被阻塞,将会造成其他任务没机会执行。所以强烈建议使用后两个方法,根据任务类型不同,主动创建线程池,进行资源隔离,避免互相干扰。
1.3 设置任务结果
CompletableFuture
提供以下方法,可以主动设置任务结果。
boolean complete(T value)boolean completeExceptionally(Throwable ex)
第一个方法,主动设置 CompletableFuture
任务执行结果,若返回 true
,表示设置成功。如果返回 false
,设置失败,这是因为任务已经执行结束,已经有了执行结果。
示例代码如下:
// 执行异步任务
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {System.out.println("cf 任务执行开始");sleep(10, TimeUnit.SECONDS);System.out.println("cf 任务执行结束");return "楼下小黑哥";
});
//
Executors.newSingleThreadScheduledExecutor().execute(() -> {sleep(5, TimeUnit.SECONDS);System.out.println("主动设置 cf 任务结果");// 设置任务结果,由于 cf 任务未执行结束,结果返回 truecf.complete("程序通事");
});
// 由于 cf 未执行结束,将会被阻塞。5 秒后,另外一个线程主动设置任务结果
System.out.println("get:" + cf.get());
// 等待 cf 任务执行结束
sleep(10, TimeUnit.SECONDS);
// 由于已经设置任务结果,cf 执行结束任务结果将会被抛弃
System.out.println("get:" + cf.get());
/**** cf 任务执行开始* 主动设置 cf 任务结果* get:程序通事* cf 任务执行结束* get:程序通事
*/
这里需要注意一点,一旦 complete
设置成功,CompletableFuture
返回结果就不会被更改,即使后续 CompletableFuture
任务执行结束。
第二个方法,给 CompletableFuture
设置异常对象。若设置成功,如果调用 get
等方法获取结果,将会抛错。
示例代码如下:
// 执行异步任务
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {System.out.println("cf 任务执行开始");sleep(10, TimeUnit.SECONDS);System.out.println("cf 任务执行结束");return "楼下小黑哥";
});
//
Executors.newSingleThreadScheduledExecutor().execute(() -> {sleep(5, TimeUnit.SECONDS);System.out.println("主动设置 cf 异常");// 设置任务结果,由于 cf 任务未执行结束,结果返回 truecf.completeExceptionally(new RuntimeException("啊,挂了"));
});
// 由于 cf 未执行结束,前 5 秒将会被阻塞。后续程序抛出异常,结束
System.out.println("get:" + cf.get());
/**** cf 任务执行开始* 主动设置 cf 异常* java.util.concurrent.ExecutionException: java.lang.RuntimeException: 啊,挂了* ......*/
1.4 CompletionStage
CompletableFuture
分别实现两个接口 Future
与 CompletionStage
。
Future
接口大家都比较熟悉,这里主要讲讲 CompletionStage
。
CompletableFuture
大部分方法来自CompletionStage
接口,正是因为这个接口,CompletableFuture
才有如从强大功能。
想要理解 CompletionStage
接口,我们需要先了解任务的时序关系的。我们可以将任务时序关系分为以下几种:
- 串行执行关系
- 并行执行关系
- AND 汇聚关系
- OR 汇聚关系
1.5 串行执行关系
任务串行执行,下一个任务必须等待上一个任务完成才可以继续执行。
CompletionStage
有四组接口可以描述串行这种关系,分别为:
thenApply
方法需要传入核心参数为 Function<T,R>
类型。这个类核心方法为:
R apply(T t)
所以这个接口将会把上一个任务返回结果当做入参,执行结束将会返回结果。
thenAccept
方法需要传入参数对象为 Consumer<T>
类型,这个类核心方法为:
void accept(T t)
返回值 void
可以看出,这个方法不支持返回结果,但是需要将上一个任务执行结果当做参数传入。
thenRun
方法需要传入参数对象为 Runnable
类型,这个类大家应该都比较熟悉,核心方法既不支持传入参数,也不会返回执行结果。
thenCompose
方法作用与 thenApply
一样,只不过 thenCompose
需要返回新的 CompletionStage
。这么理解比较抽象,可以集合代码一起理解。
方法中带有 Async ,代表可以异步执行,这个系列还有重载方法,可以传入自定义的线程池,上图未展示,读者只可以自行查看 API。
最后我们通过代码展示 thenApply
使用方式:
CompletableFuture<String> cf= CompletableFuture.supplyAsync(() -> "hello,楼下小黑哥")// 1.thenApply(s -> s + "@程序通事") // 2.thenApply(String::toUpperCase); // 3
System.out.println(cf.join());
// 输出结果 HELLO,楼下小黑哥@程序通事
这段代码比较简单,首先我们开启一个异步任务,接着串行执行后续两个任务。任务 2 需要等待任务1 执行完成,任务 3 需要等待任务 2。
上面方法,大家需要记住了
Function<T,R>
,Consumer<T>
,Runnable
三者区别,根据场景选择使用。
1.6 AND 汇聚关系
AND 汇聚关系代表所有任务完成之后,才能进行下一个任务。
如上所示,只有任务 A 与任务 B 都完成之后,任务 C 才会开始执行。
CompletionStage
有以下接口描述这种关系。
thenCombine
方法核心参数 BiFunction
,作用与 Function
一样,只不过 BiFunction
可以接受两个参数,而 Function
只能接受一个参数。
thenAcceptBoth
方法核心参数BiConsumer
作用也与 Consumer
一样,不过其需要接受两个参数。
runAfterBoth
方法核心参数最简单,上面已经介绍过,不再介绍。
这三组方法只能完成两个任务 AND 汇聚关系,如果需要完成多个任务汇聚关系,需要使用 CompletableFuture#allOf
,不过这里需要注意,这个方法是不支持返回任务结果。
AND 汇聚关系相关示例代码,开头已经使用过了,这里再粘贴一下,方便大家理解:
1.7 OR 汇聚关系
有 AND 汇聚关系,当然也存在 OR 汇聚关系。OR 汇聚关系代表只要多个任务中任一任务完成,就可以接着接着执行下一任务。
CompletionStage
有以下接口描述这种关系:
前面三组接口方法传参与 AND 汇聚关系一致,这里也不再详细解释了。
当然 OR 汇聚关系可以使用 CompletableFuture#anyOf
执行多个任务。
下面示例代码展示如何使用 applyToEither
完成 OR 关系。
CompletableFuture<String> cf= CompletableFuture.supplyAsync(() -> {sleep(5, TimeUnit.SECONDS);return "hello,楼下小黑哥";
});// 1CompletableFuture<String> cf2 = cf.supplyAsync(() -> {sleep(3, TimeUnit.SECONDS);return "hello,程序通事";
});
// 执行 OR 关系
CompletableFuture<String> cf3 = cf2.applyToEither(cf, s -> s);// 输出结果,由于 cf2 只休眠 3 秒,优先执行完毕
System.out.println(cf2.join());
// 结果:hello,程序通事
1.8 异常处理
CompletableFuture
方法执行过程若产生异常,当调用 get
,join
获取任务结果才会抛出异常。
上面代码我们显示使用 try..catch
处理上面的异常。不过这种方式不太优雅,CompletionStage
提供几个方法,可以优雅处理异常。
exceptionally
使用方式类似于 try..catch
中 catch
代码块中异常处理。
whenComplete
与 handle
方法就类似于 try..catch..finanlly
中 finally
代码块。无论是否发生异常,都将会执行的。这两个方法区别在于 handle
支持返回结果。
下面示例代码展示 handle
用法:
CompletableFuture<Integer>f0 = CompletableFuture.supplyAsync(() -> (7 / 0)).thenApply(r -> r * 10).handle((integer, throwable) -> {// 如果异常存在,打印异常,并且返回默认值if (throwable != null) {throwable.printStackTrace();return 0;} else {// 如果return integer;}});System.out.println(f0.join());
/***java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero* .....* * 0*/
0x02. 总结
JDK8 提供 CompletableFuture
功能非常强大,可以编排异步任务,完成串行执行,并行执行,AND 汇聚关系,OR 汇聚关系。
不过这个类方法实在太多,且方法还需要传入各种函数式接口,新手刚开始使用会直接会被弄懵逼。这里帮大家在总结一下三类核心参数的作用
Function
这类函数接口既支持接收参数,也支持返回值Consumer
这类接口函数只支持接受参数,不支持返回值Runnable
这类接口不支持接受参数,也不支持返回值
搞清楚函数参数作用以后,然后根据串行,AND 汇聚关系,OR 汇聚关系归纳一下相关方法,这样就比较好理解了
最后再贴一下,文章开头的思维导图,希望对你有帮助。
0x03. 帮助文档
- 极客时间-并发编程专栏
- https://colobu.com/2016/02/29/Java-CompletableFuture
- https://www.ibm.com/developerworks/cn/java/j-cf-of-jdk8/index.html
最后说一句(求关注)
CompletableFuture
很早之前就有关注,本以为跟 Future
一样,使用挺简单,谁知道学的时候才发现好难。各种 API 方法看的头有点大。
后来看到极客时间-『并发编程』专栏使用归纳方式分类 CompletableFuture
各种方法,一下子就看懂了。所这篇文章也参考这种归纳方式。
这篇文章找资料,整理一个星期,幸好今天顺利产出。
看在小黑哥写的这么辛苦的份上,点个关注吧,赏个赞呗。别下次一定啊,大哥!写文章很辛苦的,需要来点正反馈。
才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注~
欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn
编程老司机带你玩转 CompletableFuture 异步编程相关推荐
- 老司机带你玩转SDL(一)
老司机带你玩转SDL --第一站"缘由" 啦啦啦,啦啦啦 我是SDL的老司机 大厂小厂曾呆过 今天的内容真正好 带着大家把SDL玩转了 ---- 伴随着类似聂耳<卖报歌> ...
- 老司机带你玩转面试(2):Redis 过期策略以及缓存雪崩、击穿、穿透
前文回顾 建议前一篇文章没看过的同学先看下前面的文章: 「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」 过期策略 Redis 的过期策略都有哪些? 在聊这个问题之前,一定 ...
- 金士顿固态硬盘不认盘修复_#原创新人#老司机带你玩转PC,故障之SSD篇 篇一:金士顿 V300 240G SATA3 固态硬盘 丢盘掉速解决记录...
#原创新人#老司机带你玩转PC,故障之SSD篇 篇一:金士顿 V300 240G SATA3 固态硬盘 丢盘掉速解决记录 2016-10-25 11:14:08 12点赞 72收藏 23评论 小编注: ...
- 导航编程用c语言还是c加加,C语言/C加加大神程序员老司机带你玩转C语言指针详解...
很多初学编程的小伙伴都会选择C语言作为第一门学习的编程语言,因为C语言作为一门底层基础语言相对于其他的高层语言来说更加容易学习.可以来帮助正在学习编程的小伙伴更加快速的了解计算机原理. 但是初学C语言 ...
- 老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化
引言 今天周末,我在家坐着掐指一算,马上又要到一年一度的金九银十招聘季了,国内今年上半年受到 YQ 冲击,金三银四泡汤了,这就直接导致很多今年毕业的同学会和明年毕业的同学一起参加今年下半年的秋招,这个 ...
- 老司机带你玩转网盘,就是这么简单暴力
小伙伴们,小曹哥来了.互联网分享引流的大咖有很多,小曹只是他们的一个搬用工,肯定没有大咖们做的好,请大咖们多多担待,也请在上小曹评论区留下珍贵的意见,我会虚心向各位学习.好了,今天的主题现在开始,我们 ...
- 资深老司机带你玩转-测试用例
一.测试用例-模板 所属模块 相关需求 用例标题 前置条件 步骤 预期 优先级 用例类型 /MKT(#1000) [MKT-需求]XXXXXXX [9.9_MKT_WEB]优惠列表,XXXXXXXXX ...
- 老司机带你玩转git (四) git的协同合作
大家都知道,git被戏称为全球最大的同性的交友平台.在这里,不仅能完成各种项目的学习,更能完成各种姿势的学习(呜呜呜呜呜~小火车经过啦).那么为了更好的开车(学习).我想我们有必要学习pull req ...
- 十年建站老司机带你十分钟搭建网站
十年建站老司机带你十分钟搭建网站 本文概要: 1. 域名 + 域名注册 + 域名备案 + 域名解析绑定 2. 服务器 + 虚拟主机 + windows服务器 + linux服务器 3. CMS建站系统 ...
- 纵剑仙界一直显示连接服务器,纵剑仙界h5新手指引 老司机带你少走弯路
纵剑仙界h5新手指引,老司机带你少走弯路.新手初来乍到,不知道该做什么怎么办?别怕!让小编来手把手教你玩转纵剑仙界! 在<纵剑仙界>手游中,等级是一个非常重要的因素.除了转生.装备高阶装备 ...
最新文章
- 从 Word2Vec 到 BERT
- datagrid出现相同两组数据_数据分析之统计学
- Spring集成–第1节– Hello World
- sql存储过程----备份数据库
- array数组的若干操作
- Xamarin.Android 隐藏软键盘
- win7小工具打不开_Win7系统电脑桌面双击快捷键方式图标没反应的解决办法
- 把txt格式数据制作成xml数据
- 手游与App测试如何快速转型? —— 过来人科普手游与App测试四大区别
- spark SQL压缩入门
- 别再用 Visio 了!试试这个比它快 10 倍的画图工具不香吗?
- leaflet 加载海量点位,点击marker 查看详情功能
- ppt背景图片php,ppt背景图片怎么设置 ppt幻灯片制作视频
- 【高数】极限运算法则+两个重要极限
- win7 把html作为桌面,微软开始部署桌面HTML5版必应Bing
- 手赚网试玩平台源码 可封装APP 带文章资讯功能 帝国cms7.5内核
- C语言经典问题——兑换硬币
- 全景分割相关论文写作与准备笔记
- 什么叫诚实_诚信是什么
- iOS:error: exportArchive:No certificate for team ‘xxx‘ matching ‘‘xxx found
热门文章
- 使用java语言实现将10进制转化为2进制
- linux modprobe.conf怎么不见了—-CentOS 6
- c#引用参数ref与输出参数out
- 领域驱动设计的个人理解
- oracle数据库速度测试,ORACLE数据库测试数据插入速度
- java 句柄无效_sql报句柄无效。 (异常来自 HRESULT:0x80070006 (E_HANDLE))
- java 启动顺序_java语句执行顺序
- 【视频】时间序列分析:ARIMA-ARCH / GARCH模型分析股票价格
- 拓端tecdat|R语言中基于混合数据抽样(MIDAS)回归的HAR-RV模型预测GDP增长
- 实验三 函数的最值与导数