要理解 CompletableFuture,首先要弄懂什麼是 Future。由於後者是前者的擴展。本文並不打算詳細的介紹 Future,畢竟不是本文的重點。html
Future是java1.5增長的一個接口,提供了一種異步並行計算的能力。好比說主線程須要執行一個複雜耗時的計算任務,咱們能夠經過future把這個任務放在獨立的線程(池)中執行,而後主線程繼續處理其餘任務,處理完成後再經過Future獲取計算結果。java
這裏經過一個簡單的示例帶你理解下 Future。數據庫
咱們有兩個服務,一個是用戶服務能夠獲取用戶信息,一個是地址服務,能夠經過用戶id獲取地址信息。以下,編程
@AllArgsConstructor @Data public class PersonInfo { private long id; private String name; private int age; }
public class PersonService { public PersonInfo getPersonInfo(Long personId) throws InterruptedException { Thread.sleep(500);//模擬調用耗時 //真實項目中,這裏大部分時候是經過dao從數據庫獲取 return new PersonInfo(personId, "zhangsan", 30); //返回一條 } }
@AllArgsConstructor public class AddressInfo { private String addressLine; private String city; private String province; private String country; }
public class AddressService { public AddressInfo getAddress(long personId) throws InterruptedException { Thread.sleep(600); //模擬調用耗時 System.out.println("id:" + personId); //真實項目中,這裏大部分時候是經過dao從數據庫獲取 return new AddressInfo("勝利大街143號", "北京市", "北京", "中國"); } }
而後咱們演示下如何在主線程使用 Future 來進行異步調用。segmentfault
public class FutureTest { public static void main(String[] args) throws InterruptedException, ExecutionException { long startTime = System.currentTimeMillis(); PersonService personService = new PersonService(); AddressService addressService = new AddressService(); long personId = 100L; //調用用戶服務獲取用戶基本信息 FutureTask<PersonInfo> personInfoFutureTask = new FutureTask<>(new Callable<PersonInfo>() { @Override public PersonInfo call() throws Exception { return personService.getPersonInfo(personId); } }); new Thread(personInfoFutureTask).start(); Thread.sleep(300); //模擬主線程其它操做耗時 FutureTask<AddressInfo> addressInfoFutureTask = new FutureTask<>(new Callable<AddressInfo>() { @Override public AddressInfo call() throws Exception { return addressService.getAddress(personId); } }); new Thread(addressInfoFutureTask).start(); PersonInfo personInfo = personInfoFutureTask.get();//獲取我的信息結果 AddressInfo addressInfo = addressInfoFutureTask.get();//獲取地址信息結果 System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms"); } }
輸出:設計模式
總共用時909ms
很明顯,若是咱們不使用 Future,而是在主線程串行的調用,耗時會是 500 + 300 + 600 = 1400
毫秒。經過 Future提供的異步計算功能,咱們能夠多個任務並行的執行,從而提升執行效率。框架
我但願你能仔細的看上面的這個示例,由於後面講到 CompletableFuture 我會使用同一個示例。異步
經過上面的例子來看,彷佛 Future 自己已經很強大了。那麼 CompletableFuture 又是作啥的呢?ide
雖然Future以及相關使用方法提供了異步執行任務的能力,可是對於結果的獲取倒是很不方便,只能經過阻塞或者輪詢的方式獲得任務的結果。在上面的示例中,personInfoFutureTask.get()
就是阻塞調用,在線程獲取結果以前get方法會一直阻塞。異步編程
輪詢的方式在上面的示例中沒有,其實也很簡單。是Future提供了一個isDone方法,咱們能夠在程序中不斷的輪詢這個方法查詢執行結果。
可是,不管是阻塞方式仍是輪詢方式,都不夠好。
正是在這樣的背景下,CompletableFuture 在java8橫空出世。CompletableFuture提供了一種機制可讓任務執行完成後通知監聽的一方,相似設計模式中的觀察者模式。
首先咱們來看看,上面Future那個示例,若是是用 CompletableFuture 該怎麼作?
我先給出代碼,
public class CompetableFutureTest { public static void main(String[] args) throws InterruptedException, ExecutionException { long startTime = System.currentTimeMillis(); PersonService personService = new PersonService(); AddressService addressService = new AddressService(); long personId = 100L; CompletableFuture<PersonInfo> personInfoCompletableFuture = CompletableFuture.supplyAsync(() -> personService.getPersonInfo(personId)); CompletableFuture<AddressInfo> addressInfoCompletableFuture = CompletableFuture.supplyAsync(() -> addressService.getAddress(personId)); Thread.sleep(300); //模擬主線程其它操做耗時 PersonInfo personInfo = personInfoCompletableFuture.get();//獲取我的信息結果 AddressInfo addressInfo = addressInfoCompletableFuture.get();//獲取地址信息結果 System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms"); } }
首先咱們實現一樣的功能代碼簡潔不少。supplyAsync 支持異步地執行咱們指定的方法,這個例子中的異步執行方法是調用service。咱們也可使用 Executor 執行異步程序,默認是 ForkJoinPool.commonPool()。
另外經過這個示例,能夠發現咱們徹底可使用 CompletableFuture 代替 Future。
固然 CompletableFuture 的功能遠不止與此,否則它的存在就沒有意義了。CompletableFuture 提供了幾十種方法輔助咱們操做異步任務,用好了這些方法能夠寫出更加簡潔,高效的代碼。好比下面這個例子:
CompletableFuture<PersonInfo> personInfoCompletableFuture = CompletableFuture.supplyAsync(() -> personService.getPersonInfo(personId)); CompletableFuture<AddressInfo> addressInfoCompletableFuture = CompletableFuture.supplyAsync(() -> addressService.getAddress(personId)); final CompletableFuture<Void> completableFutureAllOf = CompletableFuture.allOf(personInfoCompletableFuture, addressInfoCompletableFuture); completableFutureAllOf.get(); //執行時間以最長那個任務爲準 PersonInfo personInfo = personInfoCompletableFuture.get();//立刻返回 AddressInfo addressInfo = addressInfoCompletableFuture.get();//立刻返回
這個示例中,allOf可讓咱們把多個異步任務結果的獲取整合起來,這樣操做更簡單,代碼更簡潔。
前面提到了它能夠解決的痛點,就是提供了一種相似觀察者模式的機制,當異步的計算結果完成後能夠通知監聽者。下面來看個示例,
CompletableFuture<PersonInfo> personInfoCompletableFuture = CompletableFuture.supplyAsync(() -> personService.getPersonInfo(personId)); CompletableFuture<AddressInfo> addressInfoCompletableFuture = CompletableFuture.supplyAsync(() -> addressService.getAddress(personId)); final CompletableFuture<Void> completableFutureAllOf = CompletableFuture.allOf(personInfoCompletableFuture, addressInfoCompletableFuture); //監聽執行結果,整合兩個任務的結果進一步處理 final CompletableFuture<PersonAndAddress> personAndAddressCompletableFuture = completableFutureAllOf.thenApply((voidInput) -> new PersonAndAddress(personInfoCompletableFuture.join(), addressInfoCompletableFuture.join())); personAndAddressCompletableFuture.join();//以時間長的任務爲準
在上面這個示例中,當兩個異步任務執行完畢後,咱們能夠經過thenApply監聽到結果並進行處理。
CompletableFuture 還有不少好玩有用的功能,若是感興趣能夠自行研究下。
經過前面的講解,你應該對 Future 以及它的擴展接口 CompletableFuture 都有了比較深刻的認識。
我我的的建議是若是你的項目是基於java8,大部分狀況你應該用後者而不是前者。若是你的項目是java8以前的版本,也建議你使用第三方的工具好比 Guava 等框架提供的Future工具類。
參考:
關注公衆號:犀牛飼養員的技術筆記
csdn博客: https://blog.csdn.net/pony_ma...