本文从实例出发,介绍 CompletableFuture
基本用法。不过讲的再多,不如亲自上手练习一下。因此建议各位小伙伴看完,上机练习一把,快速掌握 CompletableFuture
。html
我的博文地址:sourl.cn/s5MbCmjava
全文摘要:编程
Future
VS CompletableFuture
CompletableFuture
基本用法一些业务场景咱们须要使用多线程异步执行任务,加快任务执行速度。 Java 提供 Runnable
Future<V>
两个接口用来实现异步任务逻辑。多线程
虽然 Future<V>
能够获取任务执行结果,可是获取方式十方不变。咱们不得不使用Future#get
阻塞调用线程,或者使用轮询方式判断 Future#isDone
任务是否结束,再获取结果。并发
这两种处理方式都不是很优雅,JDK8 以前并发类库没有提供相关的异步回调实现方式。没办法,咱们只好借助第三方类库,如 Guava
,扩展 Future
,增长支持回调功能。相关代码以下:app
虽然这种方式加强了 Java 异步编程能力,可是仍是没法解决多个异步任务须要相互依赖的场景。异步
举一个生活上的例子,假如咱们须要出去旅游,须要完成三个任务:ide
很显然任务一和任务二没有相关性,能够单独执行。可是任务三必须等待任务一与任务二结束以后,才能订购租车服务。异步编程
为了使任务三时执行时能获取到任务一与任务二执行结果,咱们还须要借助 CountDownLatch
。函数
JDK8 以后,Java 新增一个功能十分强大的类:CompletableFuture
。单独使用这个类就能够轻松的完成上面的需求:
你们能够先不用管
CompletableFuture
相关API
,下面将会具体讲解。
对比 Future<V>
,CompletableFuture
优势在于:
怎么样,是否是功能很强大?接下来抓稳了,小黑哥要发车了。
首先来经过 IDE 查看下这个类提供的方法:
稍微数一下,这个类总共有 50 多个方法,个人天。。。
不过也不要怕,小黑哥帮大家概括好了,跟着小黑哥的节奏,带大家掌握 CompletableFuture
。
若图片不清晰,能够关注『程序通事』,回复:『233』,获取该思惟导图
建立 CompletableFuture
对象实例咱们可使用以下几个方法:
第一个方法建立一个具备默认结果的 CompletableFuture
,这个没啥好讲。咱们重点讲述下下面四个异步方法。
前两个方法 runAsync
不支持返回值,而 supplyAsync
能够支持返回结果。
这个两个方法默认将会使用公共的 ForkJoinPool
线程池执行,这个线程池默认线程数是 CPU 的核数。
能够设置 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数
使用共享线程池将会有个弊端,一旦有任务被阻塞,将会形成其余任务没机会执行。因此强烈建议使用后两个方法,根据任务类型不一样,主动建立线程池,进行资源隔离,避免互相干扰。
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 任务未执行结束,结果返回 true
cf.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 任务未执行结束,结果返回 true
cf.completeExceptionally(new RuntimeException("啊,挂了"));
});
// 因为 cf 未执行结束,前 5 秒将会被阻塞。后续程序抛出异常,结束
System.out.println("get:" + cf.get());
/*** * cf 任务执行开始 * 主动设置 cf 异常 * java.util.concurrent.ExecutionException: java.lang.RuntimeException: 啊,挂了 * ...... */
复制代码
CompletableFuture
分别实现两个接口 Future
与 CompletionStage
。
Future
接口你们都比较熟悉,这里主要讲讲 CompletionStage
。
CompletableFuture
大部分方法来自CompletionStage
接口,正是由于这个接口,CompletableFuture
才有如从强大功能。
想要理解 CompletionStage
接口,咱们须要先了解任务的时序关系的。咱们能够将任务时序关系分为如下几种:
任务串行执行,下一个任务必须等待上一个任务完成才能够继续执行。
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
三者区别,根据场景选择使用。
AND 汇聚关系表明全部任务完成以后,才能进行下一个任务。
如上所示,只有任务 A 与任务 B 都完成以后,任务 C 才会开始执行。
CompletionStage
有如下接口描述这种关系。
thenCombine
方法核心参数 BiFunction
,做用与 Function
同样,只不过 BiFunction
能够接受两个参数,而 Function
只能接受一个参数。
thenAcceptBoth
方法核心参数BiConsumer
做用也与 Consumer
同样,不过其须要接受两个参数。
runAfterBoth
方法核心参数最简单,上面已经介绍过,再也不介绍。
这三组方法只能完成两个任务 AND 汇聚关系,若是须要完成多个任务汇聚关系,须要使用 CompletableFuture#allOf
,不过这里须要注意,这个方法是不支持返回任务结果。
AND 汇聚关系相关示例代码,开头已经使用过了,这里再粘贴一下,方便你们理解:
有 AND 汇聚关系,固然也存在 OR 汇聚关系。OR 汇聚关系表明只要多个任务中任一任务完成,就能够接着接着执行下一任务。
CompletionStage
有如下接口描述这种关系:
前面三组接口方法传参与 AND 汇聚关系一致,这里也再也不详细解释了。
固然 OR 汇聚关系可使用 CompletableFuture#anyOf
执行多个任务。
下面示例代码展现如何使用 applyToEither
完成 OR 关系。
CompletableFuture<String> cf
= CompletableFuture.supplyAsync(() -> {
sleep(5, TimeUnit.SECONDS);
return "hello,楼下小黑哥";
});// 1
CompletableFuture<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,程序通事
复制代码
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 */
复制代码
JDK8 提供 CompletableFuture
功能很是强大,能够编排异步任务,完成串行执行,并行执行,AND 汇聚关系,OR 汇聚关系。
不过这个类方法实在太多,且方法还须要传入各类函数式接口,新手刚开始使用会直接会被弄懵逼。这里帮你们在总结一下三类核心参数的做用
Function
这类函数接口既支持接收参数,也支持返回值Consumer
这类接口函数只支持接受参数,不支持返回值Runnable
这类接口不支持接受参数,也不支持返回值搞清楚函数参数做用之后,而后根据串行,AND 汇聚关系,OR 汇聚关系概括一下相关方法,这样就比较好理解了
最后再贴一下,文章开头的思惟导图,但愿对你有帮助。
CompletableFuture
很早以前就有关注,本觉得跟 Future
同样,使用挺简单,谁知道学的时候才发现好难。各类 API 方法看的头有点大。
后来看到极客时间-『并发编程』专栏使用概括方式分类 CompletableFuture
各类方法,一会儿就看懂了。所这篇文章也参考这种概括方式。
这篇文章找资料,整理一个星期,幸亏今天顺利产出。
看在小黑哥写的这么辛苦的份上,点个关注吧,赏个赞呗。别下次必定啊,大哥!写文章很辛苦的,须要来点正反馈。
才疏学浅,不免会有纰漏,若是你发现了错误的地方,还请你留言给我指出来,我对其加以修改。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注~
欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客:studyidea.cn