下圖表明了并發(fā)和并行的區(qū)別:
Future接口
Future接口在java5中被引入骗灶,設(shè)計初衷是對將來某個時刻會發(fā)生的結(jié)果進(jìn)行建模惨恭。它建模了一種一步計算。打個比方耙旦,你拿了一袋子衣服到你中意的干洗店去洗脱羡。干洗店的員工會給你張發(fā)票,告訴你什么時候你的衣服會洗好(這就是一個future事件)。衣服干洗的同事锉罐,你可以去做其他的事情帆竹。Future的另一個有點(diǎn)是它比更底層的Thread更易用,要使用Future脓规,通常你只需要將耗時的操作封裝在一個Callable對象中栽连,再將他交給ExecutorService,就萬事大吉了侨舆。
ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(new Callable<Double>(){
public Double call(){
return doSomeLongComputation();
}
});
doSomethingElse();
Double result = future.get(1,TimeUnit.SECONDS);
這種編程方式讓你的線程可以在ExecutorService以并發(fā)方式調(diào)用另一個線程執(zhí)行耗時操作的同時秒紧,去執(zhí)行一些其他的任務(wù)。接著挨下,如果你已經(jīng)運(yùn)行到?jīng)]有異步操作的結(jié)果就無法繼續(xù)任何有意義的工作時熔恢,可以調(diào)用他的get方法去獲取操作的結(jié)果。如果操作已經(jīng)完成臭笆,該方法就會立刻返回操作的結(jié)果叙淌,否則它會阻塞線程,直到操作完成愁铺,返回相應(yīng)的結(jié)果鹰霍。
如果長時間運(yùn)行的操作永遠(yuǎn)不反回了會怎樣?為了處理這種可能性帜讲,雖然Future提供了一個無需任何參數(shù)的get方法衅谷,還是推薦使用重載的版本椒拗,它接受一個超時的參數(shù)似将,通過它,可以定義線程等待Future結(jié)果的最長時間蚀苛。
Future接口的局限性
通過第一個例子在验,知道了Future接口提供了方法來檢測異步計算是否已經(jīng)結(jié)束(使用isDone()方法),等待異步操作結(jié)束堵未,以及獲取計算的結(jié)果腋舌。但是這些特性還不足以讓你編寫簡潔的并發(fā)代碼。比如渗蟹,我們很難表述Future結(jié)果之間的依賴性;從文字描述上很簡單块饺,“當(dāng)長時間計算任務(wù)完成時,請將該計算的結(jié)果通知到另一個長時間運(yùn)行的計算任務(wù)雌芽,這兩個計算任務(wù)都完成后時授艰,請將該計算的結(jié)果通知到另一個長時間運(yùn)行的計算任務(wù),這兩個計算任務(wù)都完成后,將計算的結(jié)果與另一個查詢操作結(jié)果合并”世落。但是淮腾,使用Future中提供的方法完成這樣的操作又是另一回事。這也是我們需要更具描述能力的特性的原因,比如下面這些:
- 將兩個異步計算合并為一個——這兩個異步計算之間互相獨(dú)立谷朝,同時第二個又依賴于第一個的結(jié)果
- 等待Future集合中的所有任務(wù)都完成
- 僅等待Future集合中最快結(jié)束的任務(wù)完成(有可能因為它們師徒通過不同的方式計算同一個值)洲押,并返回它的結(jié)果。
- 通過編程方式完成一個Future任務(wù)的執(zhí)行(即以手工設(shè)定異步操作結(jié)果的方式)
- 應(yīng)對Future的完成事件(即當(dāng)Fureu的完成事件發(fā)生時會受到通知圆凰,并能使用Future計算的結(jié)果進(jìn)行下一步的操作杈帐,不止是簡單地紫色等待操作的結(jié)果)
使用CompletableFuture構(gòu)建異步應(yīng)用
為了展示CompletableFuture的強(qiáng)大特性,我們會創(chuàng)建一個名為“最佳加個查詢器”的應(yīng)用专钉,他會查詢多個在線商店娘荡,依據(jù)給定的產(chǎn)品或服務(wù)找出最低的價格。這個過程中驶沼,你會學(xué)到幾個重要的技能
- 首先炮沐,你會學(xué)到如何為你的客戶提供異步API(如果你擁有一間在線商店的話,這是非常有幫助的)
- 其次回怜,你會掌握如何讓你使用了同步API的代碼變?yōu)榉亲枞a大年。你會了解如何使用流水線將兩個連續(xù)的異步操作合并為一個異步計算操作。這種情況肯定會出現(xiàn)玉雾,比如翔试,在線商店返回了你想要購買商品的原始價格,并附帶著一個折扣代碼——最終复旬,要計算出該商品的實際價格垦缅,你不得不訪問第二個遠(yuǎn)程折扣服務(wù),長須改折扣代碼對應(yīng)的折扣比率驹碍。
- 你還會學(xué)到如何以響應(yīng)式的方式處理異步操作的完成事件壁涎,以及隨著各個商店返回他的商品價格,最佳價格查詢器如何持續(xù)地更新每種商品的最佳推薦志秃,而不是等待所有的商店都返回他們各自的價格(這種方式存在著一定的風(fēng)險怔球,一旦某件商店的服務(wù)中斷,用戶可能遭受白屏)浮还。
實現(xiàn)異步API
為了實現(xiàn)最佳價格查詢器應(yīng)用,讓我們從每個商店都應(yīng)該提供的API定義入手竟坛。首先,商店應(yīng)該聲明依據(jù)指定產(chǎn)品名稱返回價格的方法:
public class Shop{
public double getPrice(String produc){
}
}
該方法的內(nèi)部實現(xiàn)會查詢商店的數(shù)據(jù)庫钧舌,但也有可能執(zhí)行一些其他耗時的任務(wù)担汤,比如聯(lián)系其他外部服務(wù)。所以會有耗時任務(wù)洼冻,我們在剩下的內(nèi)容中崭歧,采用delay方法模擬這些長期運(yùn)行的方法的執(zhí)行,它會人為地引入1秒鐘的延遲碘赖。
public static void delay(){
try{
Thread.sleep(1000L);
}catch(InterruptedException e){
thorw new RuntimeExcepiton(e);
}
}
在getPrice方法中引入一個模擬的延遲
public double getPirce(String product){
return calculatePrice(product);
}
private double calculatePrice(String product){
delay();
return random.nextDouble() * product.charAt(0) + product.charAt(1);
}
很明顯驾荣,這個API的使用者外构,調(diào)用該方法時,它依舊會被阻塞播掷。為等待同步事件完成而等待1秒鐘审编,這是無法接受的。
將同步方法轉(zhuǎn)換為異步方法
public Future<Double> getPriceAsync(String product){
CompletableFuture<Double> futurePrice = new CompletableFuture<>();//創(chuàng)建Completablefuture對象歧匈,會包含計算結(jié)果
new Thread(()->{
double price = calculatePrice(product);//在另一個線程中以異步方式執(zhí)行計算
fururePrice.complete(price);//需長時間計算的任務(wù)結(jié)束并得出結(jié)果時垒酬,設(shè)置future的返回值。
}).start()
return futurePrice;//無需等待還沒結(jié)束的計算件炉,直接返回Future對象勘究。
}
錯誤處理
如果沒有意外,我們目前開發(fā)的代碼工作的很正常斟冕。但是口糕,如果價格計算過程中產(chǎn)生了錯誤會怎么樣?非常不幸磕蛇,這種情況下你會得到一個相當(dāng)糟糕的結(jié)果:用于提示錯誤的異常會被限制在試圖計算商品價格的當(dāng)前線程的范圍內(nèi)景描,最終會殺死線程。而這會導(dǎo)致等待get方法返回結(jié)果的客戶端會被永久的阻塞秀撇。
public Future<Double> getPriceAsync(String product){
CompletableFuture<Double> futurePrice = new CompletableFuture<>();//創(chuàng)建Completablefuture對象超棺,會包含計算結(jié)果
new Thread(()->{
try{
double price = calculatePrice(product);//在另一個線程中以異步方式執(zhí)行計算
fururePrice.complete(price);//需長時間計算的任務(wù)結(jié)束并得出結(jié)果時,設(shè)置future的返回值呵燕。
}catch(Exception e){
futurePrice.completeExceptionally(e);
}
}).start()
return futurePrice;//無需等待還沒結(jié)束的計算棠绘,直接返回Future對象。
}
使用工廠方法supplyAsync創(chuàng)建CompletableFuture
目前為止我們已經(jīng)了解了如何通過編程創(chuàng)建安CompletableFuture對象以及如何獲取返回值再扭,雖然看起來這些操作已經(jīng)比較方便氧苍,但還有進(jìn)一步的提升空間,CompletableFuture類自身提供了大量精巧的工廠方法霍衫,使用這些方法能更容易地萬恒整個流程候引。
使用工廠方法supplyAsync創(chuàng)建CompletableFure對象
public Future<Double> getPriceAsync(String product){
return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}