詳解 java CompletableFuture

背景知識

要理解 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方法,咱們能夠在程序中不斷的輪詢這個方法查詢執行結果。

可是,不管是阻塞方式仍是輪詢方式,都不夠好。

  • 阻塞的方式和異步編程的初衷相違背
  • 輪詢的方式會耗費無謂的CPU資源

正是在這樣的背景下,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工具類。

參考:


關注公衆號:犀牛飼養員的技術筆記

我的博客:http://www.machengyu.net

csdn博客: https://blog.csdn.net/pony_ma...

思否: https://segmentfault.com/u/ma...

相關文章
相關標籤/搜索