一、並發與並行
異步,陌生而熟悉的詞匯,做開發的都知道
二、java1.5的Future接口
Future接口在Java 5中被引入,設計初衷是對將來某個時刻會發生的結果進行建模。它建模了一種異步計算,返回一個執行運算結果的引用,當運算結束后,這個引用被返回給調用方。在Future中觸發那些潛在耗時的操作把調用線程解放出來,讓它能繼續執行其他有價值的工作,不再需要呆呆等待耗時的操作完成。打個比方,你可以把它想象成這樣的場景:你拿了一袋子衣服到你中意的干洗店去洗。干洗店的員工會給你張發票,告訴你什么時候你的衣服會洗好(這就是一個Future事件)。衣服干洗的同時,你可以去做其他的事情。Future的另一個優點是它比更底層的Thread更易用。要使用Future,通常你只需要將耗時的操作封裝在一個Callable對象中,再將它提交給ExecutorService,就萬事大吉了。下面這段代碼展示了Java 8之前使用Future的一個例子。
ExecutorService executor = Executors.newCachedThreadPool(); Future<Double> future = executor.submit(new Callable<Double>() { public Double call() { return doSomeLongComputation(); }}); doSomethingElse(); try { Double result = future.get(1, TimeUnit.SECONDS); } catch (ExecutionException ee) { // 計算拋出一個異常 } catch (InterruptedException ie) { // 當前線程在等待過程中被中斷 } catch (TimeoutException te) { // 在Future對象完成之前超過已過期 }
Future的局限性
通過第一個例子,我們知道Future接口提供了方法來檢測異步計算是否已經結束(使用isDone方法),等待異步操作結束,以及獲取計算的結果。但是這些特性還不足以讓你編寫簡潔的並發代碼。比如,我們很難表述Future結果之間的依賴性;從文字描述上這很簡單,“當長時間計算任務完成時,請將該計算的結果通知到另一個長時間運行的計算任務,這兩個計算任務都完成后,將計算的結果與另一個查詢操作結果合並”。但是,使用Future中提供的方法完成這樣的操作又是另外一回事。這也是我們需要更具描述能力的特性的原因,比如下面這些。
1、 將兩個異步計算合並為一個——這兩個異步計算之間相互獨立,同時第二個又依賴於第
一個的結果。
2、等待Future集合中的所有任務都完成。
3、僅等待Future集合中最快結束的任務完成(有可能因為它們試圖通過不同的方式計算同
一個值),並返回它的結果。
4、通過編程方式完成一個Future任務的執行(即以手工設定異步操作結果的方式)。
5、應對Future的完成事件(即當Future的完成事件發生時會收到通知,並能使用Future計算的結果進行下一步的操作,不只是簡單地阻塞等待操作的結果)。
三、強大的CompletableFuture
廢話不說,直接上代碼
public class Shop { public Future<Double> getPriceAsync(String product){
// 創建CompletableFuture對象,他會包含計算結果 CompletableFuture<Double> futurePrice = new CompletableFuture<>();
// 在另一個線程中異步執行 new Thread(() -> { double price = calculatePrice(product);
// 需長時間計算的任務結果並得出結果時,設置Future的返回值 futurePrice.complete(price); }).start();
// 無需等待還沒結束的計算,直接返回結果 return futurePrice; } private double calculatePrice(String product){ delay(); return new Random().nextDouble() * product.charAt(0) + product.charAt(1); } public static void delay(){ try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ } } public static void main(String[] args) { Shop shop = new Shop(); long startTime = System.currentTimeMillis(); Future<Double> priceAsync = shop.getPriceAsync("my favorite product"); long invocationTime = ((System.currentTimeMillis() - startTime) / 1_000_000); System.out.println("invocation return after " + invocationTime + "msecs"); delay(); try { double price = priceAsync.get(); System.out.println("price is %.2f&n" + price); }catch (Exception e){ throw new RuntimeException(e); } } }
錯誤處理:
如果沒有意外,我們目前開發的代碼工作得很正常。但是,如果價格計算過程中產生了錯誤會怎樣呢?非常不幸,這種情況下你會得到一個相當糟糕的結果:用於提示錯誤的異常會被限制在試圖計算商品價格的當前線程的范圍內,最終會殺死該線程,而這會導致等待get方法返回結果的客戶端永久地被阻塞。客戶端可以使用重載版本的get方法,它使用一個超時參數來避免發生這樣的情況。這是一種值得推薦的做法,你應該盡量在你的代碼中添加超時判斷的邏輯,避免發生類似的問題。使用這種方法至少能防止程序永久地等待下去,超時發生時,程序會得到通知發生了Timeout-Exception。不過,也因為如此,你不會有機會發現計算商品價格的線程內到底發生了什么問題才引發了這樣的失效。為了讓客戶端能了解商店無法提供請求商品價格的原因,你需要使用CompletableFuture的completeExcep-tionally方法將導致CompletableFuture內發生問題的異常拋出。對代碼清單11-4優化后的結果如下所示。
public Future<Double> getPriceAsync(String product) { CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread( () -> { try { double price = calculatePrice(product); futurePrice.complete(price); } catch (Exception ex) {
// 將異常包裝返回 futurePrice.completeExceptionally(ex); } }).start(); return futurePrice; }