Java8增強的Future:CompletableFuture

CompletableFuture是Java8新增的一個超大型工具類,為什麼說她大呢?因為一方面它實現了Future接口,更重要的是,它實現了CompletionStage接口.這個接口也是Java8新增加的,而CompletionStage擁有多達約40種方法,


通過CompletableFuture提供進一步封裝,我們很容易實現Future模式那樣的異步調用,例如:


Java8增強的Future:CompletableFuture

上述代碼中CompletableFuture.supplyAsync()方法構造了一個CompletableFuture實例,在supplyAsync()函數中,他會在一個新的線程中,執行傳入的參數.在這裡,他會執行calc()方法,而calc()方法的執行可能是比較慢的,但是不影響CompletableFuture實例的構造速度,因此supplyAsync()會立即返回,他返回的CompletableFuture對象實例就可以作為這次調用的契約,在將來任何場合,用於獲得最終的計算結果.在CompletableFuture中,類似的工廠方法有以下幾個:


Java8增強的Future:CompletableFuture

其中supplyAsync()方法用於那些需要返回值的場景,比如計算某個數據,而runAsync()方法用於沒有返回值的場景,比如,僅僅是簡單地執行某一個異步動作.

首先說明一下已Async結尾的方法都是可以異步執行的,如果指定了線程池,會在指定的線程池中執行,如果沒有指定,默認會在ForkJoinPool.commonPool()中執行

在這兩對方法中,都有一個方法可以接手一個Executor參數,這使我們可以讓Supplier或者Runnable在指定的線程池工作,如果不指定,則在默認的系統公共的ForkJoinPool.common線程池中執行.

流式調用

在前文中我已經簡單的提到CompletionStage的約40個接口為函數式編程做準備的,在這裡,就讓我們看一下,如果使用這些接口進行函數式的流式API調用


Java8增強的Future:CompletableFuture

上述代碼中,使用supplyAsync()函數執行了一個異步任務,接著連續使用流式調用對任務處理結果進行在加工,直到最後的結果輸出:

CompletableFuture中的異常處理


Java8增強的Future:CompletableFuture

組合多個CompletableFuture

CompletableFuture

還允許你將多個CompletableFuture進行組合,一種方法是使用thenCompose(),它的方法簽名如下:


Java8增強的Future:CompletableFuture

另外一種組和多個CompletableFuture的方法是thenCombine()它的簽名如下:


Java8增強的Future:CompletableFuture

實現異步API


Java8增強的Future:CompletableFuture

將同步方法裝換為異步方法


Java8增強的Future:CompletableFuture

錯誤處理

上述代碼,如果沒有意外,可以正常工作,但是如果價格計算過程中生產了錯誤會怎樣呢?非常不幸,這種情況下你會得到一個相當糟糕的結果:

用於提示錯誤的異常會限制在視圖計算商品的價格的當前線程的範圍內,最終會殺死該線程,而這會導致等待get方法放回結果的客戶端永久的被阻塞,而這會導致等待get方法放回結果的客戶端永久的被阻塞, 為了讓客戶端能瞭解商店無法提供請求商品價格的原因.我們對代碼優化,!


Java8增強的Future:CompletableFuture

使用工廠方法supplyAsync創建CompletableFuture


Java8增強的Future:CompletableFuture

讓代碼免受阻塞之苦


Java8增強的Future:CompletableFuture


使用平行流對請求進行並行操作


Java8增強的Future:CompletableFuture

相當不錯,看起來這是個簡單有效的主意,對4個不同商店的查詢實現了並行.所有完成操作的總耗時只有1秒多一點,讓我們嘗試使用CompletableFuture,將findprices

方法中對不同商店的同步調用替換為異步調用.

使用CompletableFuture發起異步請求


Java8增強的Future:CompletableFuture

結果讓我們失望了.我們採用異步調用新版方法,和並行差不多

尋找更好的方案

經過我增加商店數量,然後使用三種方式反覆的測試,發現了一個問題,並行流和異步調用的性能不分伯仲,究其原因都一樣,它們內部採用的是同樣的通用線程池,默認都使用固定數目的線程,具體線程數取決於

Runtime.getRuntime.availableProcessors()反回值,然而,CompletableFuture具有一定的優勢,因為它允許你對執行器進行配置,尤其是線程池的大小,讓它以適合應用需求的方式進行配置,滿足程序的要求,而這是並行流API無法提供的.


Java8增強的Future:CompletableFuture

經過測試處理5個商店 是1秒多,處理9個商店也是1秒多

並行–使用流還是CompletableFutures?

目前為止,我們已經知道對集合進行並行計算有兩種方式,要麼將其轉化為並行流,利用map這樣的操作開展工作,要麼枚舉出集合中的每一個元素,創建新的線程,在CompletableFuture內對其進行操作,後者提供了更多的靈活性,你可以調整線程池大小,二者能幫助你確保整體計算機不會因為線程都在等待I/O而發生阻塞 我們使用這些API的建議如下:

  • 如果你進行的是計算密集型的操作,並且沒有I/O,那麼推薦使用Stream接口,因為實現簡單,同時效率也可能是最高的
  • 反之,如果你並行的工作單元還涉及等待I/O的操作(包括網絡連接等待).那麼使用CompletableFuture是靈活性更好,你可以像前面討論的那樣,依據等待/計算,或者W/C的比率設定需要使用的線程數,


分享到:


相關文章: