Java 8:正在运行的CompletableFuture
在Java 8中全面研究了CompletableFuture API之后,我們準(zhǔn)備編寫(xiě)一個(gè)簡(jiǎn)單的Web搜尋器。 我們已經(jīng)使用ExecutorCompletionService , Guava ListenableFuture和Scala / Akka解決了類(lèi)似的問(wèn)題。 我選擇了相同的問(wèn)題,以便輕松比較方法和實(shí)現(xiàn)技術(shù)。
首先,我們將定義一個(gè)簡(jiǎn)單的阻止方法來(lái)下載單個(gè)URL的內(nèi)容:
沒(méi)有什么花哨。 稍后將為線程池內(nèi)的其他站點(diǎn)調(diào)用此方法。 另一種方法將String解析為XML Document (讓我省略實(shí)現(xiàn),沒(méi)有人愿意看一下它):
private Document parse(String xml) //...最后,我們算法的核心是以Document為輸入的每個(gè)網(wǎng)站的功能計(jì)算相關(guān)性 。 就像上面我們不在乎實(shí)現(xiàn)一樣,只有簽名很重要:
private CompletableFuture<Double> calculateRelevance(Document doc) //...讓我們把所有的東西放在一起。 抓取到一份網(wǎng)站列表后,我們的搜尋器應(yīng)開(kāi)始異步并發(fā)下載每個(gè)網(wǎng)站的內(nèi)容。 然后,將每個(gè)下載HTML字符串解析為XML Document并隨后計(jì)算相關(guān)性 。 最后,我們采用所有計(jì)算出的相關(guān)性指標(biāo)并找到最大的指標(biāo)。 當(dāng)您意識(shí)到下載內(nèi)容和計(jì)算相關(guān)性都是異步的(返回CompletableFuture )并且我們絕對(duì)不想阻塞或忙于等待時(shí),這聽(tīng)起來(lái)很簡(jiǎn)單。 這是第一部分:
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());實(shí)際上這里有很多事情。 定義要爬網(wǎng)的線程池和站點(diǎn)是顯而易見(jiàn)的。 但是這種鏈?zhǔn)奖磉_(dá)式計(jì)算relevanceFutures 。 最后的map()和collect()的序列具有很強(qiáng)的描述性。 從網(wǎng)站列表開(kāi)始,我們通過(guò)將異步任務(wù)( downloadSite() )提交到線程池中,將每個(gè)網(wǎng)站( String )轉(zhuǎn)換為CompletableFuture<String> 。
因此,我們有了CompletableFuture<String> 。 我們繼續(xù)對(duì)其進(jìn)行轉(zhuǎn)換,這一次在每個(gè)parse()上都應(yīng)用了parse()方法。 請(qǐng)記住,當(dāng)基礎(chǔ)將來(lái)完成時(shí), thenApply()將調(diào)用提供的lambda并立即返回CompletableFuture<Document> 。 第三個(gè)也是最后一個(gè)轉(zhuǎn)換步驟是使用calculateRelevance()將輸入列表中的每個(gè)CompletableFuture<Document>組成。 請(qǐng)注意, calculateRelevance()返回CompletableFuture<Double>而不是Double ,因此我們使用thenCompose()而不是thenApply() 。 經(jīng)過(guò)這么多階段,我們終于collect()了CompletableFuture<Double> 。
現(xiàn)在,我們想對(duì)所有結(jié)果進(jìn)行一些計(jì)算。 我們有一份期貨清單,我們想知道所有這些期貨(最后一個(gè))何時(shí)完成。 當(dāng)然,我們可以在每個(gè)將來(lái)注冊(cè)完成回調(diào),并使用CountDownLatch阻止直到調(diào)用所有回調(diào)。 我對(duì)此很懶,讓我們利用現(xiàn)有的CompletableFuture.allOf() 。 不幸的是,它有兩個(gè)小缺點(diǎn)-使用vararg而不是Collection ,并且不返回將來(lái)的合計(jì)結(jié)果,而是返回Void 。 通過(guò)匯總結(jié)果,我的意思是:如果我們提供List<CompletableFuture<Double>>該方法應(yīng)返回CompletableFuture<List<Double>> ,而不是CompletableFuture<Void> ! 幸運(yùn)的是,使用一些粘合代碼很容易修復(fù):
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())); }仔細(xì)觀察sequence()參數(shù)和返回類(lèi)型。 實(shí)現(xiàn)非常簡(jiǎn)單,訣竅是使用現(xiàn)有的allOf()但是當(dāng)allDoneFuture完成時(shí)(這意味著所有基礎(chǔ)期貨都已完成),只需遍歷所有期貨并在每個(gè)期貨上進(jìn)行join() (阻塞等待)。 但是,由于目前所有期貨都已完成,因此保證此電話(huà)不會(huì)被阻止! 有了這種實(shí)用程序,我們終于可以完成我們的任務(wù):
CompletableFuture<List<Double>> allDone = sequence(relevanceFutures); CompletableFuture<OptionalDouble> maxRelevance = allDone.thenApply(relevances ->relevances.stream().mapToDouble(Double::valueOf).max() );這很容易–當(dāng)allDone完成后,應(yīng)用我們的功能即可計(jì)算整個(gè)集合中的最大相關(guān)性。 maxRelevance仍然是未來(lái)。 到JVM到達(dá)這一行時(shí),可能尚未下載任何網(wǎng)站。 但是我們將業(yè)務(wù)邏輯封裝在期貨之上,并以事件驅(qū)動(dòng)的方式將其堆疊。 代碼保持可讀性(不帶lambda和普通Future的版本至少要長(zhǎng)兩倍),但避免阻塞主線程。 當(dāng)然, allDone也可以作為中間步驟,我們可以對(duì)其進(jìn)行進(jìn)一步的轉(zhuǎn)換,而實(shí)際上還沒(méi)有結(jié)果。
缺點(diǎn)
Java 8中的CompletableFuture是向前邁出的一大步。 從對(duì)異步任務(wù)的細(xì)微抽象到功能完善,功能豐富的實(shí)用程序。 但是,在玩了幾天之后,我發(fā)現(xiàn)了一些小缺點(diǎn):
- 返回前面提到的CompletableFuture<Void> CompletableFuture.allOf() 。 我認(rèn)為可以這樣說(shuō):如果我通過(guò)一組期貨并希望等待所有這些期貨,那么我也想在它們?nèi)菀椎竭_(dá)時(shí)提取結(jié)果。 使用CompletableFuture.anyOf()甚至更糟。 如果我等待任何期貨完成,那么我將無(wú)法想象傳遞不同類(lèi)型的期貨,比如說(shuō)CompletableFuture<Car>和CompletableFuture<Restaurant> 。 如果我不在乎哪個(gè)先完成,那么我該如何處理返回類(lèi)型? 通常,您將傳遞同類(lèi)期貨的集合(例如CompletableFuture<Car> ),然后anyOf()可以簡(jiǎn)單地返回該類(lèi)型的期貨(而不是再次代替CompletableFuture<Void> )。
- 混合可設(shè)置和可聽(tīng)的抽象。 在番石榴中,有ListenableFuture和SettableFuture擴(kuò)展。 ListenableFuture允許注冊(cè)回調(diào),而SettableFuture增加了從任意線程和上下文設(shè)置(解析)將來(lái)值的可能性。 CompletableFuture與SettableFuture等效,但是沒(méi)有等效于ListenableFuture受限版本。 為什么會(huì)出問(wèn)題呢? 如果API返回CompletableFuture ,然后有兩個(gè)線程等待它完成(這沒(méi)什么問(wèn)題),那么其中一個(gè)線程可以解決此將來(lái)并喚醒其他線程,而只有API實(shí)現(xiàn)才可以執(zhí)行此操作。 但是,當(dāng)API嘗試在以后解決將來(lái)時(shí),對(duì)complete()調(diào)用將被忽略。 它可能會(huì)導(dǎo)致真正令人討厭的錯(cuò)誤,在Guava中,將這兩個(gè)責(zé)任分開(kāi)可以避免。
- 在JDK中, CompletableFuture被忽略。 未對(duì)ExecutorService進(jìn)行改裝以返回CompletableFuture 。 從字面上看, CompletableFuture在JDK中未引用任何地方。 這是一個(gè)非常有用的類(lèi),與Future向下兼容,但在標(biāo)準(zhǔn)庫(kù)中并未真正推廣。
- 膨脹的API(?)總共50種方法,大多數(shù)為三種形式。 拆分可設(shè)置和可聽(tīng) (見(jiàn)上文)將有所幫助。 同樣,恕我直言,諸如runAfterBoth()或runAfterEither()類(lèi)的某些方法runAfterBoth()并不屬于任何CompletableFuture 。 fast.runAfterBoth(predictable, ...)和predictable.runAfterBoth(fast, ...)之間有區(qū)別嗎? 否,但是API支持兩者之一。 實(shí)際上,我相信runAfterBoth(fast, predictable, ...)更好地表達(dá)我的意圖。
- CompletableFuture.getNow(T)應(yīng)該使用Supplier<T>而不是原始引用。 在下面的示例中,無(wú)論將來(lái)是否完成, expensiveAlternative()始終是代碼: future.getNow(expensiveAlternative());
但是,我們可以輕松地調(diào)整此行為(我知道,這里有一個(gè)小的競(jìng)爭(zhēng)條件,但是原始的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();} }使用此實(shí)用程序方法,我們可以避免在不需要時(shí)調(diào)用expensiveAlternative() :
getNow(future, () -> expensiveAlternative()); //or: getNow(future, this::expensiveAlternative);
總體而言, CompletableFuture是我們JDK腰帶中的一款出色的新工具。 較小的API問(wèn)題,有時(shí)由于有限的類(lèi)型推斷而導(dǎo)致語(yǔ)法過(guò)于冗長(zhǎng),這不會(huì)阻止您使用它。 至少它為更好的抽象和更健壯的代碼奠定了堅(jiān)實(shí)的基礎(chǔ)。
翻譯自: https://www.javacodegeeks.com/2013/05/java-8-completablefuture-in-action.html
總結(jié)
以上是生活随笔為你收集整理的Java 8:正在运行的CompletableFuture的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 出口贸易备案登记表(出口贸易备案)
- 下一篇: Java StringBuilder神话