Java 8:正在运行的CompletableFuture
在Java 8中全面研究了CompletableFuture
API之后,我们准备编写一个简单的Web搜寻器。 我们已经使用ExecutorCompletionService
, Guava ListenableFuture
和Scala / Akka解决了类似的问题。 我选择了相同的问题,以便轻松比较方法和实现技术。
首先,我们将定义一个简单的阻止方法来下载单个URL的内容:
private String downloadSite(final String site) {try {log.debug("Downloading {}", site);final String res = IOUtils.toString(new URL("http://" + site), UTF_8);log.debug("Done {}", site);return res;} catch (IOException e) {throw Throwables.propagate(e);}
}
没有什么花哨。 稍后将为线程池内的其他站点调用此方法。 另一种方法将String
解析为XML Document
(让我省略实现,没有人愿意看一下它):
private Document parse(String xml) //...
最后,我们算法的核心是以Document
为输入的每个网站的功能计算相关性 。 就像上面我们不在乎实现一样,只有签名很重要:
private CompletableFuture<Double> calculateRelevance(Document doc) //...
让我们把所有的东西放在一起。 抓取到一份网站列表后,我们的搜寻器应开始异步并发下载每个网站的内容。 然后,将每个下载HTML字符串解析为XML Document
并随后计算相关性 。 最后,我们采用所有计算出的相关性指标并找到最大的指标。 当您意识到下载内容和计算相关性都是异步的(返回CompletableFuture
)并且我们绝对不想阻塞或忙于等待时,这听起来很简单。 这是第一部分:
ExecutorService executor = Executors.newFixedThreadPool(4);List<String> topSites = Arrays.asList("www.google.com", "www.youtube.com", "www.yahoo.com", "www.msn.com"
);List<CompletableFuture<Double>> relevanceFutures = topSites.stream().map(site -> CompletableFuture.supplyAsync(() -> downloadSite(site), executor)).map(contentFuture -> contentFuture.thenApply(this::parse)).map(docFuture -> docFuture.thenCompose(this::calculateRelevance)).collect(Collectors.<CompletableFuture<Double>>toList());
实际上这里有很多事情。 定义要爬网的线程池和站点是显而易见的。 但是这种链式表达式计算relevanceFutures
。 最后的map()
和collect()
的序列具有很强的描述性。 从网站列表开始,我们通过将异步任务( downloadSite()
)提交到线程池中,将每个网站( String
)转换为CompletableFuture<String>
。
因此,我们有了CompletableFuture<String>
。 我们继续对其进行转换,这一次在每个parse()
上都应用了parse()
方法。 请记住,当基础将来完成时, thenApply()
将调用提供的lambda并立即返回CompletableFuture<Document>
。 第三个也是最后一个转换步骤是使用calculateRelevance()
将输入列表中的每个CompletableFuture<Document>
组成。 请注意, calculateRelevance()
返回CompletableFuture<Double>
而不是Double
,因此我们使用thenCompose()
而不是thenApply()
。 经过这么多阶段,我们终于collect()
了CompletableFuture<Double>
。
现在,我们想对所有结果进行一些计算。 我们有一份期货清单,我们想知道所有这些期货(最后一个)何时完成。 当然,我们可以在每个将来注册完成回调,并使用CountDownLatch
阻止直到调用所有回调。 我对此很懒,让我们利用现有的CompletableFuture.allOf()
。 不幸的是,它有两个小缺点-使用vararg而不是Collection
,并且不返回将来的合计结果,而是返回Void
。 通过汇总结果,我的意思是:如果我们提供List<CompletableFuture<Double>>
该方法应返回CompletableFuture<List<Double>>
,而不是CompletableFuture<Void>
! 幸运的是,使用一些粘合代码很容易修复:
private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {CompletableFuture<Void> allDoneFuture =CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));return allDoneFuture.thenApply(v ->futures.stream().map(future -> future.join()).collect(Collectors.<T>toList()));
}
仔细观察sequence()
参数和返回类型。 实现非常简单,诀窍是使用现有的allOf()
但是当allDoneFuture
完成时(这意味着所有基础期货都已完成),只需遍历所有期货并在每个期货上进行join()
(阻塞等待)。 但是,由于目前所有期货都已完成,因此保证此电话不会被阻止! 有了这种实用程序,我们终于可以完成我们的任务:
CompletableFuture<List<Double>> allDone = sequence(relevanceFutures);
CompletableFuture<OptionalDouble> maxRelevance = allDone.thenApply(relevances ->relevances.stream().mapToDouble(Double::valueOf).max()
);
这很容易–当allDone
完成后,应用我们的功能即可计算整个集合中的最大相关性。 maxRelevance
仍然是未来。 到JVM到达这一行时,可能尚未下载任何网站。 但是我们将业务逻辑封装在期货之上,并以事件驱动的方式将其堆叠。 代码保持可读性(不带lambda和普通Future
的版本至少要长两倍),但避免阻塞主线程。 当然, allDone
也可以作为中间步骤,我们可以对其进行进一步的转换,而实际上还没有结果。
缺点
Java 8中的CompletableFuture
是向前迈出的一大步。 从对异步任务的细微抽象到功能完善,功能丰富的实用程序。 但是,在玩了几天之后,我发现了一些小缺点:
- 返回前面提到的
CompletableFuture<Void>
CompletableFuture.allOf()
。 我认为可以这样说:如果我通过一组期货并希望等待所有这些期货,那么我也想在它们容易到达时提取结果。 使用CompletableFuture.anyOf()
甚至更糟。 如果我等待任何期货完成,那么我将无法想象传递不同类型的期货,比如说CompletableFuture<Car>
和CompletableFuture<Restaurant>
。 如果我不在乎哪个先完成,那么我该如何处理返回类型? 通常,您将传递同类期货的集合(例如CompletableFuture<Car>
),然后anyOf()
可以简单地返回该类型的期货(而不是再次代替CompletableFuture<Void>
)。 - 混合可设置和可听的抽象。 在番石榴中,有
ListenableFuture
和SettableFuture
扩展。ListenableFuture
允许注册回调,而SettableFuture
增加了从任意线程和上下文设置(解析)将来值的可能性。CompletableFuture
与SettableFuture
等效,但是没有等效于ListenableFuture
受限版本。 为什么会出问题呢? 如果API返回CompletableFuture
,然后有两个线程等待它完成(这没什么问题),那么其中一个线程可以解决此将来并唤醒其他线程,而只有API实现才可以执行此操作。 但是,当API尝试在以后解决将来时,对complete()
调用将被忽略。 它可能会导致真正令人讨厌的错误,在Guava中,将这两个责任分开可以避免。 - 在JDK中,
CompletableFuture
被忽略。 未对ExecutorService
进行改装以返回CompletableFuture
。 从字面上看,CompletableFuture
在JDK中未引用任何地方。 这是一个非常有用的类,与Future
向下兼容,但在标准库中并未真正推广。 - 膨胀的API(?)总共50种方法,大多数为三种形式。 拆分可设置和可听 (见上文)将有所帮助。 同样,恕我直言,诸如
runAfterBoth()
或runAfterEither()
类的某些方法runAfterBoth()
并不属于任何CompletableFuture
。fast.runAfterBoth(predictable, ...)
和predictable.runAfterBoth(fast, ...)
之间有区别吗? 否,但是API支持两者之一。 实际上,我相信runAfterBoth(fast, predictable, ...)
更好地表达我的意图。 CompletableFuture.getNow(T)
应该使用Supplier<T>
而不是原始引用。 在下面的示例中,无论将来是否完成,expensiveAlternative()
始终是代码:future.getNow(expensiveAlternative());
但是,我们可以轻松地调整此行为(我知道,这里有一个小的竞争条件,但是原始的getNow()也可以这种方式工作):
public static <T> T getNow(CompletableFuture<T> future,Supplier<T> valueIfAbsent) throws ExecutionException, InterruptedException {if (future.isDone()) {return future.get();} else {return valueIfAbsent.get();} }
使用此实用程序方法,我们可以避免在不需要时调用
expensiveAlternative()
:getNow(future, () -> expensiveAlternative()); //or: getNow(future, this::expensiveAlternative);
总体而言, CompletableFuture
是我们JDK腰带中的一款出色的新工具。 较小的API问题,有时由于有限的类型推断而导致语法过于冗长,这不会阻止您使用它。 至少它为更好的抽象和更健壮的代码奠定了坚实的基础。
翻译自: https://www.javacodegeeks.com/2013/05/java-8-completablefuture-in-action.html
Java 8:正在运行的CompletableFuture相关推荐
- java类的运行顺序_Java语言类的基本运行顺序
本文主要向大家介绍了Java语言类的基本运行顺序,通过具体的代码向大家展示,希望对大家学习Java语言有所帮助.我们以下面的类来说明一个基本的 Java 类的运行顺序:1. public class ...
- Java程序后台运行,即使关掉Putty终端
Java程序后台运行,即使关掉Putty终端 比如,通过Putty启动远程主机上面的文件 java -cp Crawler.jar Main& 关闭Putty终端之后,这个java程序也会被中 ...
- 《Java疯狂讲义》(第3版)学习笔记 2 - Java语言的运行机制
内容 1.高级语言的运行机制 2.Java 语言的运行机制 1.高级语言的运行机制 高级语言主要分为编译型语言和解释型语言两类. 编译型语言是指使用专门的编译器.针对特定平台(操作系统)将高级语言源代 ...
- Java程序的运行原理及JVM的启动是多线程的吗?
Java程序的运行原理及JVM的启动是多线程的吗? A:Java程序的运行原理 Java通过java命令会启动java虚拟机.启动JVM,等于启动了一个应用程序,也就是启动了一个进程. 该进程会自动启 ...
- Java JIT在运行JDK代码时是否作弊?
本文翻译自:Does Java JIT cheat when running JDK code? I was benchmarking some code, and I could not get i ...
- 如何分析java程序_如何利用 JConsole观察分析Java程序的运行,进行排错调优
一.JConsole是什么 从Java 5开始 引入了 JConsole.JConsole 是一个内置 Java 性能分析器,可以从命令行或在 GUI shell 中运行.您可以轻松地使用 JCons ...
- 如何利用 JConsole观察分析Java程序的运行,进行排错调优
原文链接:http://jiajun.iteye.com/blog/810150 一.JConsole是什么 从Java 5开始 引入了 JConsole.JConsole 是一个内置 Java 性能 ...
- 【Java 虚拟机原理】JDK 体系结构 | Java 源码运行原理 | Java 虚拟机内存
文章目录 一.JDK 体系结构 二.Java 源码运行原理 三.Java 虚拟机内存结构 一.JDK 体系结构 JDK 体系结构 : 下图所有的内容都是 JDK 体系中的组成元素 ; Java Lan ...
- java隐藏方式运行,Java 数据隐藏和封装
类由一些数据和方法组成.最重要的面向对象技术之一是,是把数据隐藏再类中,只能通过方法获取.这种技术叫作 封装(encapsulation),因为它可以把数据(和内部方法)安全的密封在这个"容 ...
- 如何编译java,java如何编译运行?
对于一个Java开发者来说我们编写的程序肯定是要运行才能体现出作用来,对于新手同学来说你知道如何去编译和运行一个Java程序吗?小千今天就来告诉大家,步骤很详细带好小本本哦. Java程序编译运行步骤 ...
最新文章
- python高并发的解决方案
- python日历小程序_一个查看网络设备信息Python小程序
- 【AutoML】如何选择最合适的数据增强操作
- LeetCode --- Validate Binary Search Tree
- BZOJ.1007.[HNOI2008]水平可见直线(凸壳 单调栈)
- 【HDOJ】4602 Partition
- epoll和poll的C++11多线程练习
- module_param()函数
- mysql 查看修改连接数据库_mysql查看最大连接数和修改mysql数据库最大连接数方法...
- 调侃《Head First设计模式》之总结篇
- 应用程序虚拟化工具(VMware ThinApp)5.2.1汉化绿色企业版
- jquery option selected 无效
- win10 文件系统错误-2147416359
- 域格模块移动网络信号指标介绍
- 使用python语言,编写一段代码,通过gadl读取tif影像数据并将wgs84坐标进行UTM投影...
- 植物大战僵尸音乐计算机简谱,植物大战僵尸(主题音乐)钢琴谱
- 正则“^[a-zA-Z]” 和 “[^a-zA-Z]”的区别
- 别人问我:为什么程序员都不善言辞?惭愧啊!
- 网络经济与企业管理【一】之企业管理概论
- jumpserver文件的上传和下载
热门文章
- linux wait函数头文件_手把手教Linux驱动9-等待队列waitq
- Ubuntu下MySQL、Redis以及MongoDB三个数据库的启动、重启以及停止命令
- streaming api_通过Spring Integration消费Twitter Streaming API
- php cdi_CDI和lambda的策略模式
- oracle中悲观锁定_如何使用悲观锁定修复乐观锁定竞争条件
- matchers依赖_Hamcrest Matchers的高级创建
- 尺度不变性是指什么不变_不变性如何提供帮助
- 对象空指针_可选和对象:空指针救星!
- java akka_用于大型事件处理的Akka Java
- 面向切面编程应用_应用面向方面的编程