Java8CompletableFuture详解剖析.docx

上传人:b****4 文档编号:24279088 上传时间:2023-05-26 格式:DOCX 页数:13 大小:21.59KB
下载 相关 举报
Java8CompletableFuture详解剖析.docx_第1页
第1页 / 共13页
Java8CompletableFuture详解剖析.docx_第2页
第2页 / 共13页
Java8CompletableFuture详解剖析.docx_第3页
第3页 / 共13页
Java8CompletableFuture详解剖析.docx_第4页
第4页 / 共13页
Java8CompletableFuture详解剖析.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

Java8CompletableFuture详解剖析.docx

《Java8CompletableFuture详解剖析.docx》由会员分享,可在线阅读,更多相关《Java8CompletableFuture详解剖析.docx(13页珍藏版)》请在冰豆网上搜索。

Java8CompletableFuture详解剖析.docx

Java8CompletableFuture详解剖析

Java8CompletableFuture详解

Java8来了,是时候学一下新的东西了。

Java7和Java6只不过是稍作修改的版本,而Java8将会发生重大的改进。

或许是Java8太大了吧?

今天我会给你彻底地解释JDK8中的新的抽象–CompletableFuture。

众所周知,Java8不到一年就会发布,因此这篇文章是基于JDK8build88withlambdasupport的。

CompletableFutureextendsFuture提供了方法,一元操作符和促进异步性以及事件驱动编程模型,它并不止步于旧版本的Java中。

如果你打开JavaDocofCompletableFuture你一定会感到震惊。

大约有五十种方法(!

),而且它们中的一些非常有意思而且不好理解,例如:

复制代码代码如下:

publicCompletableFuturethenCombineAsync(

CompletableFuture

extendsU>other,

BiFunction

superT,?

superU,?

extendsV>fn,

Executorexecutor)

不必担心,继续读下去。

CompletableFuture收集了所有ListenableFutureinGuava和SettableFuture的特征。

此外,内置的lambda表达式使它更接近于Scala/Akkafutures。

这听起来好得令人难以置信,但是请继续读下去。

CompletableFuture有两个主要的方面优于ol中的Future–异步回调/转换,这能使得从任何时刻的任何线程都可以设置CompletableFuture的值。

一、提取、修改包装的值

通常futures代表其它线程中运行的代码,但事实并非总是如此。

有时你想要创造一个Future来表示你知道将会发生什么,例如JMSmessagearrival。

所以你有Future但是未来并没有潜在的异步工作。

你只是想在未来JMS消息到达时简单地完成(解决),这是由一个事件驱动的。

在这种情况下,你可以简单地创建CompletableFuture来返还给你的客户端,只要你认为你的结果是可用的,仅仅通过complete()就能解锁所有等待Future的客户端。

首先你可以简单地创建新的CompletableFuture并且给你的客户端:

复制代码代码如下:

publicCompletableFutureask(){

finalCompletableFuturefuture=newCompletableFuture<>();

//...

returnfuture;

}

注意这个future和Callable没有任何联系,没有线程池也不是异步工作。

如果现在客户端代码调用ask().get()它将永远阻塞。

如果寄存器完成回调,它们就永远不会生效了。

所以关键是什么?

现在你可以说:

复制代码代码如下:

plete("42")

…此时此刻所有客户端Future.get()将得到字符串的结果,同时完成回调以后将会立即生效。

当你想代表Future的任务时是非常方便的,而且没有必要去计算一些执行线程的任务上。

CompletableFplete()只能调用一次,后续调用将被忽略。

但也有一个后门叫做CompletableFuture.obtrudeValue(…)覆盖一个新Future之前的价值,请小心使用。

有时你想要看到信号发生故障的情况,如你所知Future对象可以处理它所包含的结果或异常。

如果你想进一步传递一些异常,可以用CompletableFpleteExceptionally(ex)(或者用obtrudeException(ex)这样更强大的方法覆盖前面的异常)。

completeExceptionally()也能解锁所有等待的客户端,但这一次从get()抛出异常。

说到get(),也有CompletableFuture.join()方法在错误处理方面有着细微的变动。

但总体上,它们都是一样的。

最后也有CompletableFuture.getNow(valueIfAbsent)方法没有阻塞但是如果Future还没完成将返回默认值,这使得当构建那种我们不想等太久的健壮系统时非常有用。

最后static的方法是用completedFuture(value)来返回已经完成Future的对象,当测试或者写一些适配器层时可能非常有用。

二、创造和获取CompletableFuture

好了,那么手动地创建CompletableFuture是我们唯一的选择吗?

不一定。

就像一般的Futures,我们可以关联存在的任务,同时CompletableFuture使用工厂方法:

复制代码代码如下:

staticCompletableFuturesupplyAsync(Suppliersupplier);

staticCompletableFuturesupplyAsync(Suppliersupplier,Executorexecutor);

staticCompletableFuturerunAsync(Runnablerunnable);

staticCompletableFuturerunAsync(Runnablerunnable,Executorexecutor);

无参方法Executor是以…Async结尾同时将会使用ForkJoinPmonPool()(全局的,在JDK8中介绍的通用池),这适用于CompletableFuture类中的大多数的方法。

runAsync()易于理解,注意它需要Runnable,因此它返回CompletableFuture作为Runnable不返回任何值。

如果你需要处理异步操作并返回结果,使用Supplier:

复制代码代码如下:

finalCompletableFuturefuture=CompletableFuture.supplyAsync(newSupplier(){

@Override

publicStringget(){

//...longrunning...

return"42";

}

},executor);

但是别忘了,Java8里面还有lambdas表达式呢!

复制代码代码如下:

finalCompletableFuturefuture=CompletableFuture.supplyAsync(()->{

//...longrunning...

return"42";

},executor);

或者:

复制代码代码如下:

finalCompletableFuturefuture=

CompletableFuture.supplyAsync(()->longRunningTask(params),executor);

虽然这篇文章不是关于Lambda的,但是我会相当频繁地使用lambda表达式。

三、转换和作用于CompletableFuture(thenApply)

我说过CompletableFuture优于Future但是你还不知道为什么吗?

简单说,因为CompletableFuture是一个原子也是一个因子。

我说的这句话没什么帮助吗?

Scala和JavaScript都允许future完成时允许注册异步回调,直到它准备好我们才要等待和阻止它。

我们可以简单地说:

运行这个函数时就出现了结果。

此外,我们可以叠加这些功能,把多个future组合在一起等。

例如如果我们从String转为Integer,我们可以转为在不关联的前提下从CompletableFuture到CompletableFuture

这是通过thenApply()的方法:

复制代码代码如下:

CompletableFuturethenApply(Function

superT,?

extendsU>fn);

CompletableFuturethenApplyAsync(Function

superT,?

extendsU>fn);

CompletableFuturethenApplyAsync(Function

superT,?

extendsU>fn,Executorexecutor);

如前所述...Async版本提供对CompletableFuture的大多数操作,因此我将在后面的部分中跳过它们。

记住,第一个方法将在future完成的相同线程中调用该方法,而剩下的两个将在不同的线程池中异步地调用它。

让我们来看看thenApply()的工作流程:

java;gutter:

true;first-line:

1;highlight:

[];html-script:

false">

CompletableFuturef1=//...

CompletableFuturef2=f1.thenApply(Integer:

:

parseInt);

CompletableFuturef3=f2.thenApply(r->r*r*Math.PI);

或在一个声明中:

复制代码代码如下:

CompletableFuturef3=

f1.thenApply(Integer:

:

parseInt).thenApply(r->r*r*Math.PI);

这里,你会看到一个序列的转换,从String到Integer再到Double。

但最重要的是,这些转换既不立即执行也不停止。

这些转换既不立即执行也不停止。

他们只是记得,当原始f1完成他们所执行的程序。

如果某些转换非常耗时,你可以提供你自己的Executor来异步地运行他们。

注意,此操作相当于Scala中的一元map。

四、运行完成的代码(thenAccept/thenRun)

复制代码代码如下:

CompletableFuturethenAccept(Consumer

superT>block);

CompletableFuturethenRun(Runnableaction);

在future的管道里有两种典型的“最终”阶段方法。

他们在你使用future的值的时候做好准备,当thenAccept()提供最终的值时,thenRun执行Runnable,这甚至没有方法去计算值。

例如:

复制代码代码如下:

future.thenAcceptAsync(dbl->log.debug("Result:

{}",dbl),executor);

log.debug("Continuing");

…Async变量也可用两种方法,隐式和显式执行器,我不会过多强调这个方法。

thenAccept()/thenRun()方法并没有发生阻塞(即使没有明确的executor)。

它们像一个事件侦听器/处理程序,你连接到一个future时,这将执行一段时间。

”Continuing”消息将立即出现,尽管future甚至没有完成。

五、单个CompletableFuture的错误处理

到目前为止,我们只讨论计算的结果。

那么异常呢?

我们可以异步地处理它们吗?

当然!

复制代码代码如下:

CompletableFuturesafe=

future.exceptionally(ex->"Wehaveaproblem:

"+ex.getMessage());

exceptionally()接受一个函数时,将调用原始future来抛出一个异常。

我们会有机会将此异常转换为和Future类型的兼容的一些值来进行恢复。

safe进一步的转换将不再产生一个异常而是从提供功能的函数返回一个String值。

一个更加灵活的方法是handle()接受一个函数,它接收正确的结果或异常:

复制代码代码如下:

CompletableFuturesafe=future.handle((ok,ex)->{

if(ok!

=null){

returnInteger.parseInt(ok);

}else{

log.warn("Problem",ex);

return-1;

}

});

handle()总是被调用,结果和异常都非空,这是个一站式全方位的策略。

六、一起结合两个CompletableFuture

异步处理过程之一的CompletableFuture非常不错但是当多个这样的futures以各种方式组合在一起时确实显示了它的强大。

七、结合(链接)这两个futures(thenCompose())

有时你想运行一些future的值(当它准备好了),但这个函数也返回了future。

CompletableFuture足够灵活地明白我们的函数结果现在应该作为顶级的future,对比CompletableFuture

方法thenCompose()相当于Scala的flatMap:

复制代码代码如下:

CompletableFuturethenCompose(Function

superT,CompletableFuture>fn);

…Async变化也是可用的,在下面的事例中,仔细观察thenApply()(map)和thenCompose()(flatMap)的类型和差异,当应用calculateRelevance()方法返回CompletableFuture:

复制代码代码如下:

CompletableFuturedocFuture=//...

CompletableFuture>f=

docFuture.thenApply(this:

:

calculateRelevance);

CompletableFuturerelevanceFuture=

docFuture.thenCompose(this:

:

calculateRelevance);

//...

privateCompletableFuturecalculateRelevance(Documentdoc)//...

thenCompose()是一个重要的方法允许构建健壮的和异步的管道,没有阻塞和等待的中间步骤。

八、两个futures的转换值(thenCombine())

当thenCompose()用于链接一个future时依赖另一个thenCombine,当他们都完成之后就结合两个独立的futures:

复制代码代码如下:

CompletableFuturethenCombine(CompletableFuture

extendsU>other,BiFunction

superT,?

superU,?

extendsV>fn)

…Async变量也是可用的,假设你有两个CompletableFuture,一个加载Customer另一个加载最近的Shop。

他们彼此完全独立,但是当他们完成时,您想要使用它们的值来计算Route。

这是一个可剥夺的例子:

复制代码代码如下:

CompletableFuturecustomerFuture=loadCustomerDetails(123);

CompletableFutureshopFuture=closestShop();

CompletableFuturerouteFuture=

customerFuture.thenCombine(shopFuture,(cust,shop)->findRoute(cust,shop));

//...

privateRoutefindRoute(Customercustomer,Shopshop)//...

请注意,在Java8中可以用(cust,shop)->findRoute(cust,shop)简单地代替this:

:

findRoute方法的引用:

复制代码代码如下:

customerFuture.thenCombine(shopFuture,this:

:

findRoute);

你也知道,我们有customerFuture和shopFuture。

那么routeFuture包装它们然后“等待”它们完成。

当他们准备好了,它会运行我们提供的函数来结合所有的结果(findRoute())。

当两个基本的futures完成并且findRoute()也完成时,这样routeFuture将会完成。

九、等待所有的CompletableFutures完成

如果不是产生新的CompletableFuture连接这两个结果,我们只是希望当完成时得到通知,我们可以使用thenAcceptBoth()/runAfterBoth()系列的方法,(…Async变量也是可用的)。

它们的工作方式与thenAccept()和thenRun()类似,但是是等待两个futures而不是一个:

复制代码代码如下:

CompletableFuturethenAcceptBoth(CompletableFuture

extendsU>other,BiConsumer

superT,?

superU>block)

CompletableFuturerunAfterBoth(CompletableFuture

>other,Runnableaction)

想象一下上面的例子,这不是产生新的CompletableFuture,你只是想要立刻发送一些事件或刷新GUI。

这可以很容易地实现:

thenAcceptBoth():

复制代码代码如下:

customerFuture.thenAcceptBoth(shopFuture,(cust,shop)->{

finalRouteroute=findRoute(cust,shop);

//refreshGUIwithroute

});

我希望我是错的,但也许有些人会问自己一个问题:

为什么我不能简单地阻塞这两个futures呢?

就像:

复制代码代码如下:

FuturecustomerFuture=loadCustomerDetails(123);

FutureshopFuture=closestShop();

findRoute(customerFuture.get(),shopFuture.get());

好了,你当然可以这么做。

但是最关键的一点是CompletableFuture是允许异步的,它是事件驱动的编程模型而不是阻塞并急切地等待着结果。

所以在功能上,上面两部分代码是等价的,但后者没有必要占用一个线程来执行。

十、等待第一个CompletableFuture来完成任务

另一个有趣的事是CompletableFutureAPI可以等待第一个(与所有相反)完成的future。

当你有两个相同类型任务的结果时就显得非常方便,你只要关心响应时间就行了,没有哪个任务是优先的。

API方法(…Async变量也是可用的):

复制代码代码如下:

CompletableFutureacceptEither(CompletableFuture

extendsT>other,Consumer

superT>block)

CompletableFuturerunAfterEither(CompletableFuture

>other,Runnableaction)

作为一个例子,你有两个系统可以集成。

一个具有较小的平均响应时间但是拥有高的标准差,另一个一般情况下较慢,但是更加容易预测。

为了两全其美(性能和可预测性)你可以在同一时间调用两个系统并等着谁先完成。

通常这会是第一个系统,但是在进度变得缓慢时,第二个系统就可以在可接受的时间内完成:

复制代码代码如下:

CompletableFuturefast=fetchFast();

CompletableFuturepredictable=fetchPredictably();

fast.acceptEither(predictable,s->{

System.out.println("Result:

"+s);

});

s代表了从fetchFast()或是fetchPredictably()得到的String。

我们不必知道也无需关心。

十一、完整地转换第一个系统

applyToEither()算是acceptEither()的前辈了。

当两个futures快要完成时,后者只是简单地调用一些代码片段,applyToEither()将会返回一个新的future。

当这两个最初的futures完成时,新的future也会完成。

API有点类似于(…Async变量也是可用的):

复制代码代码如下:

CompletableFutureapplyToEither(CompletableFuture

extendsT>other,Function

superT,U>fn)

这个额外的fn功能在第一个future被调用时能完成。

我不确定这个专业化方法的目的是什么,毕竟一个人可以简单地使用:

fast.applyToEither(predictable).thenApply(fn)。

因为我们坚持用这个API,但我们的确不需要额外功能的应用程序,我会简单地使用Function.identity()占位符:

复制代码代码如下:

CompletableFuturefast=fetchFast();

CompletableFuturepredictable=fetchPredictably();

CompletableFuturefirstDone

展开阅读全文
相关搜索

当前位置:首页 > 初中教育 > 中考

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1