Java8新的非同步程式設計方式 CompletableFuture(三)

Tony沈哲發表於2017-10-21

前面兩篇文章已經整理了CompletableFuture大部分的特性,本文會整理完CompletableFuture餘下的特性,以及將它跟RxJava進行比較。

3.6 Either

Either 表示的是兩個CompletableFuture,當其中任意一個CompletableFuture計算完成的時候就會執行。

方法名 描述
acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) 當任意一個CompletableFuture完成的時候,action這個消費者就會被執行。
acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action) 當任意一個CompletableFuture完成的時候,action這個消費者就會被執行。使用ForkJoinPool
acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor) 當任意一個CompletableFuture完成的時候,action這個消費者就會被執行。使用指定的執行緒池
        Random random = new Random();

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{

            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return "from future1";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{

            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return "from future2";
        });

        CompletableFuture<Void> future =  future1.acceptEither(future2,str->System.out.println("The future is "+str));

        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製程式碼

執行結果:The future is from future1 或者 The future is from future2。
因為future1和future2,執行的順序是隨機的。

applyToEither 跟 acceptEither 類似。

方法名 描述
applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn) 當任意一個CompletableFuture完成的時候,fn會被執行,它的返回值會當作新的CompletableFuture<U>的計算結果。
applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn) 當任意一個CompletableFuture完成的時候,fn會被執行,它的返回值會當作新的CompletableFuture<U>的計算結果。使用ForkJoinPool
applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor) 當任意一個CompletableFuture完成的時候,fn會被執行,它的返回值會當作新的CompletableFuture<U>的計算結果。使用指定的執行緒池
        Random random = new Random();

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{

            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return "from future1";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{

            try {
                Thread.sleep(random.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return "from future2";
        });

        CompletableFuture<String> future =  future1.applyToEither(future2,str->"The future is "+str);

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製程式碼

執行結果也跟上面的程式類似。

3.7 其他方法

allOf、anyOf是CompletableFuture的靜態方法。

3.7.1 allOf

方法名 描述
allOf(CompletableFuture<?>... cfs) 在所有Future物件完成後結束,並返回一個future。

allOf()方法所返回的CompletableFuture,並不能組合前面多個CompletableFuture的計算結果。於是我們藉助Java 8的Stream來組合多個future的結果。

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "tony");

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "cafei");

        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "aaron");

        CompletableFuture.allOf(future1, future2, future3)
                .thenApply(v ->
                Stream.of(future1, future2, future3)
                        .map(CompletableFuture::join)
                        .collect(Collectors.joining(" ")))
                .thenAccept(System.out::print);複製程式碼

執行結果:

tony cafei aaron複製程式碼

3.7.2 anyOf

方法名 描述
anyOf(CompletableFuture<?>... cfs) 在任何一個Future物件結束後結束,並返回一個future。
        Random rand = new Random();
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(rand.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "from future1";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(rand.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "from future2";
        });
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(rand.nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "from future3";
        });

        CompletableFuture<Object> future =  CompletableFuture.anyOf(future1,future2,future3);

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }複製程式碼

使用anyOf()時,只要某一個future完成,就結束了。所以執行結果可能是"from future1"、"from future2"、"from future3"中的任意一個。

anyOf 和 acceptEither、applyToEither的區別在於,後兩者只能使用在兩個future中,而anyOf可以使用在多個future中。

3.8 CompletableFuture異常處理

CompletableFuture在執行時如果遇到異常,可以使用get()並丟擲異常進行處理,但這並不是一個最好的方法。CompletableFuture本身也提供了幾種方式來處理異常。

3.8.1 exceptionally

方法名 描述
exceptionally(Function fn) 只有當CompletableFuture丟擲異常的時候,才會觸發這個exceptionally的計算,呼叫function計算值。
        CompletableFuture.supplyAsync(() -> "hello world")
                .thenApply(s -> {
                    s = null;
                    int length = s.length();
                    return length;
                }).thenAccept(i -> System.out.println(i))
                .exceptionally(t -> {
                    System.out.println("Unexpected error:" + t);
                    return null;
                });複製程式碼

執行結果:

Unexpected error:java.util.concurrent.CompletionException: java.lang.NullPointerException複製程式碼

對上面的程式碼稍微做了一下修改,修復了空指標的異常。

        CompletableFuture.supplyAsync(() -> "hello world")
                .thenApply(s -> {
//                    s = null;
                    int length = s.length();
                    return length;
                }).thenAccept(i -> System.out.println(i))
                .exceptionally(t -> {
                    System.out.println("Unexpected error:" + t);
                    return null;
                });複製程式碼

執行結果:

11複製程式碼

3.8.2 whenComplete

whenComplete 在上一篇文章其實已經介紹過了,在這裡跟exceptionally的作用差不多,可以捕獲任意階段的異常。如果沒有異常的話,就執行action。

        CompletableFuture.supplyAsync(() -> "hello world")
                .thenApply(s -> {
                    s = null;
                    int length = s.length();
                    return length;
                }).thenAccept(i -> System.out.println(i))
                .whenComplete((result, throwable) -> {

                    if (throwable != null) {
                       System.out.println("Unexpected error:"+throwable);
                    } else {
                        System.out.println(result);
                    }

                });複製程式碼

執行結果:

Unexpected error:java.util.concurrent.CompletionException: java.lang.NullPointerException複製程式碼

跟whenComplete相似的方法是handle,handle的用法在上一篇文章中也已經介紹過。

四. CompletableFuture VS Java8 Stream VS RxJava1 & RxJava2

CompletableFuture 有很多特性跟RxJava很像,所以將CompletableFuture、Java 8 Stream和RxJava做一個相互的比較。

composable lazy resuable async cached push back pressure
CompletableFuture 支援 不支援 支援 支援 支援 支援 不支援
Stream 支援 支援 不支援 不支援 不支援 不支援 不支援
Observable(RxJava1) 支援 支援 支援 支援 支援 支援 支援
Observable(RxJava2) 支援 支援 支援 支援 支援 支援 不支援
Flowable(RxJava2) 支援 支援 支援 支援 支援 支援 支援

五. 總結

Java 8提供了一種函式風格的非同步和事件驅動程式設計模型CompletableFuture,它不會造成堵塞。CompletableFuture背後依靠的是fork/join框架來啟動新的執行緒實現非同步與併發。當然,我們也能通過指定執行緒池來做這些事情。

CompletableFuture特別是對微服務架構而言,會有很大的作為。舉一個具體的場景,電商的商品頁面可能會涉及到商品詳情服務、商品評論服務、相關商品推薦服務等等。獲取商品的資訊時(/productdetails?productid=xxx),需要呼叫多個服務來處理這一個請求並返回結果。這裡可能會涉及到併發程式設計,我們完全可以使用Java 8的CompletableFuture或者RxJava來實現。

先前的文章:
Java8新的非同步程式設計方式 CompletableFuture(一)
Java8新的非同步程式設計方式 CompletableFuture(二)

相關文章