JDK1.8 CompletableFuture異步編程(^_^)

JDK 5引入了Future模式。Future接口是Java多線程Future模式的實現,在java.util.concurrent包中,可以來進行異步計算。

Future模式是多線程設計常用的一種設計模式。Future模式可以理解成:我有一個任務,提交給了Future,Future替我完成這個任務。期間我自己可以去做任何想做的事情。一段時間之後,我就便可以從Future那兒取出結果。

Future的接口很簡單,只有五個方法。

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Future接口的方法介紹如下:

  • boolean cancel (boolean mayInterruptIfRunning) 取消任務的執行。參數指定是否立即中斷任務執行,或者等等任務結束
  • boolean isCancelled () 任務是否已經取消,任務正常完成前將其取消,則返回 true
  • boolean isDone () 任務是否已經完成。需要注意的是如果任務正常終止、異常或取消,都將返回true
  • V get () throws InterruptedException, ExecutionException 等待任務執行結束,然後獲得V類型的結果。InterruptedException 線程被中斷異常, ExecutionException任務執行異常,如果任務被取消,還會拋出CancellationException
  • V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一樣,多了設置超時時間。參數timeout指定超時時間,uint指定時間的單位,在枚舉類TimeUnit中有相關的定義。如果計 算超時,將拋出TimeoutException

一般情況下,我們會結合Callable和Future一起使用,通過ExecutorService的submit方法執行Callable,並返回Future。

ExecutorService executor = Executors.newCachedThreadPool();

        Future<String> future = executor.submit(() -> { //Lambda 是一個 callable, 提交後便立即執行,這裏返回的是 FutureTask 實例
            System.out.println("running task");
            Thread.sleep(10000);
            return "return task";
        });

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

        System.out.println("do something else");  //前面的的 Callable 在其他線程中運行着,可以做一些其他的事情

        try {
            System.out.println(future.get());  //等待 future 的執行結果,執行完畢之後打印出來
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {

        } finally {
            executor.shutdown();
        }

注意:比起future.get(),其實更推薦使用get (long timeout, TimeUnit unit) 方法,設置了超時時間可以防止程序無限制的等待future的結果。 

二. CompletableFuture介紹

public class CompletableFuture<T> implements Future<T>, CompletionStage<T>

2.1 Future模式的缺點

  • Future雖然可以實現獲取異步執行結果的需求,但是它沒有提供通知的機制,我們無法得知Future什麼時候完成;並且沒有異常處理機制

  • 要麼使用阻塞,在future.get()的地方等待future返回的結果,這時又變成同步操作。要麼使用isDone()輪詢地判斷Future是否完成,這樣會耗費CPU的資源。

2.2 CompletableFuture介紹

CompletableFuture能夠將回調放到與任務不同的線程中執行,也能將回調作爲繼續執行的同步函數,在與任務相同的線程中執行。它避免了傳統回調最大的問題,那就是能夠將控制流分離到不同的事件處理器中。

CompletableFuture彌補了Future模式的缺點。在異步的任務完成後,需要用其結果繼續操作時,無需等待。可以直接通過thenAccept、thenApply、thenCompose等方式將前面異步處理的結果進行處理並可以交給另外一個異步事件處理線程來處理。

三. CompletableFuture特性

3.1 CompletableFuture的靜態工廠方法

方法名 描述
runAsync(Runnable runnable) 使用ForkJoinPool.commonPool()作爲它的線程池執行異步代碼。
runAsync(Runnable runnable, Executor executor) 使用指定的thread pool執行異步代碼。
supplyAsync(Supplier<U> supplier) 使用ForkJoinPool.commonPool()作爲它的線程池執行異步代碼,異步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor) 使用指定的thread pool執行異步代碼,異步操作有返回值

runAsync 和 supplyAsync 方法的區別是runAsync返回的CompletableFuture是沒有返回值的。

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("Hello");
        });

        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("CompletableFuture");

而supplyAsync返回的CompletableFuture是由返回值的,下面的代碼打印了future的返回值。

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("CompletableFuture");

3.2 Completable

方法名 描述
complete(T t) 完成異步執行,並返回future的結果
completeExceptionally(Throwable ex) 異步執行不正常的結束

future.get()在等待執行結果時,程序會一直block,如果此時調用complete(T t)會立即執行。

        CompletableFuture<String> future  = CompletableFuture.supplyAsync(() -> "Hello");

        future.complete("World");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

執行結果:

World

可以看到future調用complete(T t)會立即執行。但是complete(T t)只能調用一次,後續的重複調用會失效。

如果future已經執行完畢能夠返回結果,此時再調用complete(T t)則會無效。

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        future.complete("World");

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

執行結果:

Hello

如果使用completeExceptionally(Throwable ex)則拋出一個異常,而不是一個成功的結果。

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        future.completeExceptionally(new Exception());

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

執行結果:

java.util.concurrent.ExecutionException: java.lang.Exception
...

3.3 數據轉換類方法

 

3.3.1

方法名 描述
thenApply(Function<? super T,? extends U> fn) 接受一個Function<? super T,? extends U>參數用來轉換CompletableFuture
thenApplyAsync(Function<? super T,? extends U> fn) 接受一個Function<? super T,? extends U>參數用來轉換CompletableFuture,使用ForkJoinPool
thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) 接受一個Function<? super T,? extends U>參數用來轉換CompletableFuture,使用指定的線程池

thenApply的功能相當於將CompletableFuture<T>轉換成CompletableFuture<U>。

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        future = future.thenApply(new Function<String, String>() {

            @Override
            public String apply(String s) {

                return s + " World";
            }
        }).thenApply(new Function<String, String>() {
            @Override
            public String apply(String s) {

                return s.toUpperCase();
            }
        });

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

再用lambda表達式簡化一下

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(s -> s + " World").thenApply(String::toUpperCase);

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

執行結果:

HELLO WORLD

下面的例子,展示了數據流的類型經歷瞭如下的轉換:String -> Integer -> Double。

        CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> "10")
                .thenApply(Integer::parseInt)
                .thenApply(i->i*10.0);

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

執行結果:

100.0

3.2 thenCombine方法 (thenCompose可以用於組合多個CompletableFuture,將前一個結果作爲下一個計算的參數,它們之間存在着先後順序。)

3.5 計算結果完成時的處理

當CompletableFuture完成計算結果後,我們可能需要對結果進行一些處理。

3.5.1 執行特定的Action

方法名 描述
whenComplete(BiConsumer<? super T,? super Throwable> action) 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。
whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。使用ForkJoinPool。
whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor) 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。使用指定的線程池。
        CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(s->s+" World")
                .thenApply(s->s+ "\nThis is CompletableFuture demo")
                .thenApply(String::toLowerCase)
                .whenComplete((result, throwable) -> System.out.println(result));

執行結果:

hello world
this is completablefuture demo

3.5.2 執行完Action可以做轉換

方法名 描述
handle(BiFunction<? super T, Throwable, ? extends U> fn) 當CompletableFuture完成計算結果或者拋出異常的時候,執行提供的fn
handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) 當CompletableFuture完成計算結果或者拋出異常的時候,執行提供的fn,使用ForkJoinPool。
handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) 當CompletableFuture完成計算結果或者拋出異常的時候,執行提供的fn,使用指定的線程池。
        CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> "100")
                .thenApply(s->s+"100")
                .handle((s, t) -> s != null ? Double.parseDouble(s) : 0);

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

執行結果:

100100.0

在這裏,handle()的參數是BiFunction,apply()方法返回R,相當於轉換的操作。

@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     */
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

而whenComplete()的參數是BiConsumer,accept()方法返回void。

@FunctionalInterface
public interface BiConsumer<T, U> {

    /**
     * Performs this operation on the given arguments.
     *
     * @param t the first input argument
     * @param u the second input argument
     */
    void accept(T t, U u);

    /**
     * Returns a composed {@code BiConsumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code BiConsumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);

        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}

所以,handle()相當於whenComplete()+轉換。

3.5.3 純消費(執行Action)

方法名 描述
thenAccept(Consumer<? super T> action) 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值
thenAcceptAsync(Consumer<? super T> action) 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值,使用ForkJoinPool。
thenAcceptAsync(Consumer<? super T> action, Executor executor) 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值

thenAccept()是隻會對計算結果進行消費而不會返回任何結果的方法。

        CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(s->s+" World")
                .thenApply(s->s+ "\nThis is CompletableFuture demo")
                .thenApply(String::toLowerCase)
                .thenAccept(System.out::print);

執行結果:

hello world
this is completablefuture demo

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<Throwable,? extends T> 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來實現。



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章