溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

發(fā)布時間:2022-03-03 14:05:56 來源:億速云 閱讀:130 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

概述

為了展示 CompletableFuture 的強大特性, 創(chuàng)建一個名為 best-price-finder 的應(yīng)用,它會查詢多個在線商店,依據(jù)給定的產(chǎn)品或服務(wù)找出最低的價格。

這個過程中,會學到幾個重要的技能。

  • 如何提供異步API

  • 如何讓你使用了同步API的代碼變?yōu)榉亲枞a

我們將共同學習如何使用流水線將兩個接續(xù)的異步操作合并為一個異步計算操作。 比如,在線商店返回了你想要購買的商品的原始價格,并附帶著一個折扣代碼——最終,要計算出該商品的實際價格,你不得不訪問第二個遠程折扣服務(wù),查詢該折扣代碼對應(yīng)的折扣比率

  • 如何以響應(yīng)式的方式處理異步操作的完成事件,以及隨著各個商品返回它的商品價格,最佳價格查詢器如何持續(xù)的更新每種商品的最佳推薦,而不是等待所有的商店都返回他們各自的價格(這種方式存在著一定的風險,一旦某家商店的服務(wù)中斷,用戶可能遭遇白屏)。

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

同步API VS 異步API

同步API

是對傳統(tǒng)方法的另一種稱呼:你調(diào)用了某個方法,調(diào)用方在被調(diào)用方運行的過程中會等待,被調(diào)用方運行結(jié)束返回,調(diào)用方取的了被調(diào)用方的返回值并繼續(xù)運行。

即使調(diào)用方和被調(diào)用方在不同的線程中運行,調(diào)用方還是需要等被調(diào)用方結(jié)束運行,這就是 阻塞式調(diào)用。

異步API

與同步API相反,異步API會直接返回,或者至少在被調(diào)用方計算完成之前,將它剩余的計算任務(wù)交給另一個線程去做,該線程和調(diào)用方是異步的。 這就是非阻塞調(diào)用。

執(zhí)行剩余的計算任務(wù)的線程將他的計算結(jié)果返回給調(diào)用方。 返回的方式要么通過回調(diào)函數(shù),要么由調(diào)用方再此執(zhí)行一個“等待,指導(dǎo)計算完成”的方法調(diào)用。

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

同步的困擾

為了實現(xiàn)最佳價格查詢器應(yīng)用,讓我們從每個商店都應(yīng)該提供的API定義入手。

首先,商店應(yīng)該聲明依據(jù)指定產(chǎn)品名稱返回價格的方法:

public class Shop {
	public double getPrice(String product) {
	// TODO
	}
}

該方法的內(nèi)部實現(xiàn)會查詢商店的數(shù)據(jù)庫,但也有可能執(zhí)行一些其他耗時的任務(wù),比如聯(lián)系其他外部服務(wù)。

用 delay 方法模擬這些長期運行的方法的執(zhí)行,模擬執(zhí)行1S ,方法聲明如下。

public static void delay() {
	try {
		Thread.sleep(1000L);
	} catch (InterruptedException e) {
		throw new RuntimeException(e);
	}
}

getPrice 方法會調(diào)用 delay 方法,并返回一個隨機計算的值

public double getPrice(String product) {
	return calculatePrice(product);
}
private double calculatePrice(String product) {
	delay();
	return random.nextDouble() * product.charAt(0) + product.charAt(1);
}

很明顯,這個API的使用者(這個例子中為最佳價格查詢器)調(diào)用該方法時,它依舊會被阻塞。為等待同步事件完成而等待1S,這是無法接受的,尤其是考慮到最佳價格查詢器對網(wǎng)絡(luò)中的所有商店都要重復(fù)這種操作。

接下來我們會了解如何以異步方式使用同步API解決這個問題。但是,出于學習如何設(shè)計異步API的考慮, 你希望以異步API的方式重寫這段代碼, 假裝我們還在深受這一困難的煩惱,如何以異步API的方式重寫這段代碼,讓用戶更流暢地訪問呢?


Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

實現(xiàn)異步API

將同步方法改為異步方法

為了實現(xiàn)這個目標,你首先需要將 getPrice 轉(zhuǎn)換為 getPriceAsync 方法,并修改它的返回值:

public Future<Double> getPriceAsync(String product) { ... }

我們知道 ,Java 5引入了 java.util.concurrent.Future 接口表示一個異步計算(即調(diào)用線程可以繼續(xù)運行,不會因為調(diào)用方法而阻塞)的結(jié)果 。

這意味著 Future 是一個暫時還不可知值的處理器,這個值在計算完成后,可以通過調(diào)用它的 get 方法取得。因為這樣的設(shè)計, getPriceAsync 方法才能立刻返回,給調(diào)用線程一個機會,能在同一時間去執(zhí)行其他有價值的計算任務(wù)。

新的 CompletableFuture 類提供了大量的方法,讓我們有機會以多種可能的方式輕松地實現(xiàn)這個方法,比如下面就是這樣一段實現(xiàn)代碼

【getPriceAsync方法的實現(xiàn)】

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

在這段代碼中,創(chuàng)建了一個代表異步計算的 CompletableFuture 對象實例,它在計算完成時會包含計算的結(jié)果。

接著,調(diào)用 fork 創(chuàng)建了另一個線程去執(zhí)行實際的價格計算工作,不等該耗時計算任務(wù)結(jié)束,直接返回一個 Future 實例。

當請求的產(chǎn)品價格最終計算得出時,你可以使用它的 complete 方法,結(jié)束completableFuture 對象的運行,并設(shè)置變量的值。

很顯然,這個新版 Future 的名稱也解釋了它所具有的特性。使用這個API的客戶端,可以通過下面的這段代碼對其進行調(diào)用。

【使用異步的API】

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

我們看到這段代碼中,客戶向商店查詢了某種商品的價格。由于商?提供了異步API,該次調(diào)用立刻返回了一個 Future 對象,通過該對象客戶可以在將來的某個時刻取得商品的價格。

這種方式下,客戶在進行商品價格查詢的同時,還能執(zhí)行一些其他的任務(wù),比如查詢其他家商店中商品的價格,不會呆呆的阻塞在那里等待第一家商店返回請求的結(jié)果。

最后,如果所有有意義的工作都已經(jīng)完成,客戶所有要執(zhí)行的工作都依賴于商品價格時,再調(diào)用 Future 的 get 方法。執(zhí)行了這個操作后,客戶要么獲得 Future 中封裝的值(如果異步任務(wù)已經(jīng)完成),要么發(fā)生阻塞,直到該異步任務(wù)完成,期望的值能夠訪問。

輸出

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

你一定已經(jīng)發(fā)現(xiàn) getPriceAsync 方法的調(diào)用返回遠遠早于最終價格計算完成的時間。

我們有可能避免發(fā)生客戶端被住阻塞的風險。實際上這非常簡單, Future 執(zhí)行完畢可以發(fā)出一個通知,僅在計算結(jié)果可用時執(zhí)行一個由Lambda表達式或者方法引用定義的回
調(diào)函數(shù)。

不過,我們當下不會對此進行討論,現(xiàn)在我們要解決的是另一個問題:如何正確地管理
異步任務(wù)執(zhí)行過程中可能出現(xiàn)的錯誤。

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

處理異常錯誤

如果沒有意外,我們目前開發(fā)的代碼工作得很正常。但是,如果價格計算過程中產(chǎn)生了錯誤會怎樣呢?非常不幸,這種情況下你會得到一個相當糟糕的結(jié)果:用于提示錯誤的異常會被限制在試圖計算商品價格的當前線程的范圍內(nèi),最終會殺死該線程,而這會導(dǎo)致等待 get 方法返回結(jié)果的客戶端永久的被阻塞。

客戶端可以使用重載版本的 get 方法,它使用一個超時參數(shù)來避免發(fā)生這樣的情況。這是一種值得推薦的做法,你應(yīng)該盡量在你的代碼中添加超時判斷斷的邏輯,避免發(fā)生類似的問題。

使用這種方法至少能防止程序永遠的等待下去,超時發(fā)生時,程序會得到通知發(fā)生了 Timeout-Exception 。

不過,也因為如此,你不會有機會發(fā)現(xiàn)計算商品價格的線程內(nèi)到底發(fā)生了什么問題才引發(fā)了這樣的失效。

為了讓客戶端能了解商店無法提供請求商品價格的原因,你需要使用
CompletableFuture 的 completeExceptionally 方法將導(dǎo)致 CompletableFuture 內(nèi)發(fā)生問題的異常拋出。

代碼如下

【拋出CompletableFuture內(nèi)的異?!?/strong>

Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式

客戶端現(xiàn)在會收到一個 ExecutionException 異常,該異常接收了一個包含失敗原因的Exception 參數(shù),即價格計算方法最初拋出的異常。

所以,舉例來說,如果該方法拋出了一個運行時異?!皃roduct not available”,客戶端就會得到像下面這樣一段 ExecutionException :

java.util.concurrent.ExecutionException: java.lang.RuntimeException: product
not available at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2237)
at lambdasinaction.chap11.AsyncShopClient.main(AsyncShopClient.java:14)
... 5 more
Caused by: java.lang.RuntimeException: product not available
at lambdasinaction.chap11.AsyncShop.calculatePrice(AsyncShop.java:36)
at lambdasinaction.chap11.AsyncShop.lambda$getPrice$0(AsyncShop.java:23)
at lambdasinaction.chap11.AsyncShop$$Lambda$1/24071475.run(Unknown Source)
at java.lang.Thread.run(Thread.java:744)

以上是“Java8如何使用CompletableFuture構(gòu)建異步應(yīng)用方式”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI