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> )。
  • 混合可设置可听的抽象。 在番石榴中,有ListenableFutureSettableFuture扩展。 ListenableFuture允许注册回调,而SettableFuture增加了从任意线程和上下文设置(解析)将来值的可能性。 CompletableFutureSettableFuture等效,但是没有等效于ListenableFuture受限版本。 为什么会出问题呢? 如果API返回CompletableFuture ,然后有两个线程等待它完成(这没什么问题),那么其中一个线程可以解决此将来并唤醒其他线程,而只有API实现才可以执行此操作。 但是,当API尝试在以后解决将来时,对complete()调用将被忽略。 它可能会导致真正令人讨厌的错误,在Guava中,将这两个责任分开可以避免。
  • 在JDK中, CompletableFuture被忽略。 未对ExecutorService进行改装以返回CompletableFuture 。 从字面上看, CompletableFuture在JDK中未引用任何地方。 这是一个非常有用的类,与Future向下兼容,但在标准库中并未真正推广。
  • 膨胀的API(?)总共50种方法,大多数为三种形式。 拆分可设置可听 (见上文)将有所帮助。 同样,恕我直言,诸如runAfterBoth()runAfterEither()类的某些方法runAfterBoth()并不属于任何CompletableFuturefast.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问题,有时由于有限的类型推断而导致语法过于冗长,这不会阻止您使用它。 至少它为更好的抽象和更健壮的代码奠定了坚实的基础。

参考: Java 8:我们的JCG合作伙伴 Tomasz Nurkiewicz在Java和社区博客上的实践中的CompletableFuture 。

翻译自: https://www.javacodegeeks.com/2013/05/java-8-completablefuture-in-action.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/368978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java能不能修改文件大小信息_java上机考试3

考号: 000128000091 姓名: 张天義 学号: 1040610329 班级: 本科(计算机、微电、电工、经管)->计算机科学与技术 00:13:25一、单选(共80分)1. int count1;for(int i1;i<5;i){countcounti;}System.out.println(count);上述代码执行后的输出结果是A、15B、1C、16D、5标记此题…

(网页)中的简单的遮罩层

html: <div id"test"></div><div id"log_window"> <!--<a href"javascript:cancel_shield()">关闭</a>--> <!--<img src"assets/css/images/loading.gif" />--> <p style"fo…

即使在jdk中也有错误的代码

Java 7&#xff0c;TreeSet和NullPointerException。 最近&#xff0c;我尝试用Java 7编译一个用Java 6开发的项目。在执行测试过程中发生了很多有趣的事情&#xff0c;在Java 6中使用Java 7平稳运行的测试失败了&#xff01; 因此&#xff0c;我必须理解为什么&#xff0c;这就…

Eclipse+GitHub 提交代码错误 -“rejected - non-fast-forward”

Eclipse Push出现rejected - non-fast-forward错误 在 Push到服务器时有时会出现 rejected - non-fast-forward 错误&#xff0c;这是由于远端发生改变&#xff0c;此时再提交之前你需要将远端的改变合并到本地上错误原因&#xff1a;文件冲突&#xff0c;本地的代码和远程Repo…

两天学会css基础(一)

什么是css&#xff1f;css的作用是什么&#xff1f; CSS 指层叠样式表 (Cascading Style Sheets)主要作用就是给HTML结构添加样式&#xff0c;搭建页面结构&#xff0c;比如设置元素的宽高大小&#xff0c;颜色&#xff0c;位置等等。 学习css之前先了解一下css代码在HTML中的…

在Android项目中使用AspectJ

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 转载请表明出处&#xff1a;http://www.cnblogs.com/cavalier-/p/8888459.html 什么是AOP AOP是 Aspect Oriented Programming 的缩写&#xff0c;即面向切面编程&#xff0c;和平常遇到的面向对象O…

LVM 逻辑卷 (logica volume manager)

逻辑卷轴管理员 (Logical Volume Manager) 想像一个情况&#xff0c;你在当初规划主机的时候将 /home 只给他 50G &#xff0c;等到使用者众多之后导致这个 filesystem 不够大&#xff0c; 此时你能怎么作&#xff1f; 多数的朋友都是这样&#xff1a;再加一颗新硬盘&#xff0…

java中u怎么用_Java中interrupt的使用

通常我们会有这样的需求&#xff0c;即停止一个线程。在java的api中有stop、suspend等方法可以达到目的&#xff0c;但由于这些方法在使用上存在不安全性&#xff0c;会带来不好的副作用&#xff0c;不建议被使用。具体原因可以参考Why is Thread.stop deprecated。在本文中&am…

当Maven依赖插件位于

问题&#xff1a; 我们进行了一个集成测试&#xff0c;该测试创建了一个Spring ClassPathXmlApplicationContext &#xff0c;同时这样做导致NoSuchMethodError爆炸。 事实证明&#xff0c;我们对Spring构件的依赖版本存在冲突。 这本身不是一个不寻常的问题-使用Maven依赖插件…

sql查询语句for xml path语法

【原地址】 for xml path作用&#xff1a;将多行的查询结果&#xff0c;根据某一些条件合并到一行。 例&#xff1a;现有一张表 执行下面语句 select Department,(SELECT Employee, FROM People b WHERE b.Departmenta.Department For XML Path()) Student from People as a g…

css高度已知,左右定宽,中间自适应三栏布局

css高度已知&#xff0c;左右定宽&#xff0c;中间自适应三栏布局&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale…

java使用impala存放多条sql_Impala基于内存的SQL引擎的详细介绍

数据存储使用相同的存储数据池都支持把数据存储于HDFS, HBase。元数据&#xff1a;两者使用相同的元数据SQL解释处理&#xff1a;比较相似都是通过词法分析生成执行计划。执行计划&#xff1a;Hive: 依赖于MapReduce执行框架&#xff0c;执行计划分成 map->shuffle->redu…

Android Studio打包以及Gradle配置构建

本文转载 郭霖公众号 https://mp.weixin.qq.com/s?__bizMzA5MzI3NjE2MA&mid2650241610&idx1&snb8af73f6c288b6617d9fe0ab3618118d&pass_ticketQK4j37kpmGNlsYcECWMb64HxKHEVJG5mSJubQEQguKI%3D 生成签名文件手动打包 首先生成签名文件&#xff0c;点击 Build…

去除inline-block间隙的几种方法

为什么会产生间隙&#xff1f; 由于编写代码时的美观和可读性&#xff0c;在代码中添加回车或空格而产生的间隙。 html代码&#xff1a; <ul class"container"><li></li><li></li><li></li><li></li><li&…

java重载方法math_Java语言程序设计(十二)Math数学类,方法重载及变量作用域...

1.重载方法上一篇文章用到的max方法只能用于int型数据类型&#xff0c;但是如果需要决定两个浮点数中哪个较大&#xff0c;解决方法是创建另一个方法名相同但参数不同的方法&#xff0c;代码如下&#xff1a;public static double max(double num1, double num2){if(num1>nu…

编码(转)

https://www.zhihu.com/question/28164512 关于编码和乱码的问题&#xff0c;我简单讲一下。 通常问这类问题的人是混淆了若干个不同的概念&#xff0c;并且他们自己也没有意识到自己混淆了这些概念的。 终端显示字符的编码&#xff08;windows下终端是cmd&#xff0c;linux下是…

Spring MVC:测试简介

测试是软件开发中最重要的部分之一。 井井有条的测试有助于使应用程序代码保持良好状态&#xff0c;并且处于工作状态。 有很多不同类型的测试和方法。 在本文中&#xff0c;我想对基于Spring MVC的应用程序进行单元测试进行介绍。 不要希望在这里阅读有关Spring MVC测试的全部…

yaml,json,ini这三种格式用来做配置文件优缺点

适合人类编写&#xff1a;ini > toml > yaml > json > xml > plist可以存储的数据复杂度&#xff1a;xml > yaml > toml ~ json ~ plist > ini 作者&#xff1a;赵扶摇链接&#xff1a;https://www.zhihu.com/question/41253282/answer/119857880来源&…

试验ConcurrentHashmap

我正在研究我最近的一个项目中的内存问题&#xff0c;该项目将数据保留在内存中以进行快速访问&#xff0c;但是应用程序的内存占用量非常大。 该应用程序大量使用CHM&#xff08;即Concurrenthashmap&#xff09; &#xff0c;因此&#xff0c;无需再费脑筋地猜测CHM是问题所…

CSS的position属性:relative和absolute

relative&#xff1a;是相对于自己来定位的&#xff0c;例如&#xff1a;#demo{position:relative;top:-50px;},这时#demo会在相对于它原来的位置上移50px。如果它之前的元素也为relative并有偏移&#xff0c;则两个偏移不想加&#xff0c;relative只在它原本所在位置上进行偏移…