CompletableFuture 組合式非同步程式設計

weixin_33807284發表於2019-02-23

       最近學習dubbo 原始碼, 敲了些vertx 程式碼,覺得非同步程式設計的風格是有多麼的帥,lambda表示式寫的美又很有邏輯。java 強語言能寫成這樣也是很美的一件事情。

       這遍文章簡單的看看CompletableFuture 是怎麼使用的,背後都幹了啥事,先看看dubbo上的completableFuture。Dubbo 的future 使用,請看這篇部落格http://dubbo.apache.org/en-us/blog/dubbo-new-async.html, 一直到dubbo 2.7.0 才出現 CompletableFuture,dubbo 2.6.x 會報序列化的錯誤, 因為 CompletableFuture<object> 不是一個序列化的物件,沒有 implement serialization. 

(1). 首先看看 CompletableFuture 有哪些基本的用法。CompletableFuture 是一個class,實現future 和 CompletionStage(完成階段) 介面。多個CompletionStage可以有先後順序, 可以 多個任務同時完成,可以上一個任務執行的結果傳給下個任務。有人說是執行緒編排,也有其道理。 CompletableFuture 不支援使用 callable,而支援Runnable 和 supplier, 這些都是被@FunctionalInterface 註解標註的介面。 有lambda 的表達特性。supplier 代替 callable 支援 有返回結果的非同步。 

我們看一個例子:

ExecutorService executor = Executors.newFixedThreadPool(3); // 預設可以通過ForkJoinPool 產生的執行緒來執行任務,其執行緒數是 CPU 核數➖1,不然就使用 ThreadPerTaskExecutor

Supplier<Integer> externalTask = () -> {

// do something

return 3;

}

CompletableFuture.supplyAsync(externalTask, executor). 

我們發現其本質是使用executor 執行緒 執行 externalTask的任務,任務只完成之後將結果放到 Future 中,通過get 方法獲取結果。Future get 的方法是同步還是非同步呢? 我覺得會阻塞,一直拿到正確的結果(異常, 或者 執行緒計算出來的結果), 它和 completableFuture join 方法類似。對於Runnable 介面, 可使用 runAsync方法。

(2) 那麼 CompletableFuture 如何觸發 ConpletionStage 呢?  我們來看看 

public boolean complete(T value), public boolean completeExceptionally(Throwable ex) 。 我們發現在原始碼中或者別的非同步框架中,比如前端的promise,當任務完成之後,會設定 complete,表示任務完成成功或者失敗。complete會觸發依賴他們的completionStage。下面是java 部分的程式碼,說實話我有點看不懂,好像是將依賴 放入 stack 中,然後一個一個依賴觸發


*/

final void postComplete() {

/*

* On each step, variable f holds current dependents to pop

* and run.  It is extended along only one path at a time,

* pushing others to avoid unbounded recursion.

*/

    CompletableFuture f =this; Completion h;

    while ((h = f.stack) !=null ||

(f !=this && (h = (f =this).stack) !=null)) {

CompletableFuture d; Completion t;

        if (f.casStack(h, t = h.next)) {

if (t !=null) {

if (f !=this) {

pushStack(h);

continue;

                }

h.next =null;    // detach

            }

f = (d = h.tryFire(NESTED)) ==null ?this : d;

        }

}

}。

我們看一下

public CompletableFuture<T> whenComplete(

    BiConsumer<? super T, ? super Throwable> action)。 其中action 表示回撥函式,型別是BiConsumer,接收 結果 和異常。那麼我們如何來寫一個有順序性的任務呢? completableFuture.supplyAsync(externalTask).whenComplete((result, ex) ->{

// result 是上一個task 執行的結果

//ex執行的異常

}), 但是這個方法可能會造成同步阻塞,因為 supplyAsync 和 後面的 whenComplete 是同一個執行緒執行的,當然也有可能是不是同個執行緒執行,看註冊時, supplyAsync 這個執行緒結束沒有。我們可以使用 下面的 方法來非同步執行

public CompletableFuture whenCompleteAsync(

    BiConsumer<? super T, ? super Throwable> action)

public CompletableFuture whenCompleteAsync(

    BiConsumer<? super T, ? super Throwable> action, Executor executor)    

但是如果我們只對異常結果感興趣,我們可以使用下面的方法

public CompletableFuture<T> exceptionally(

    Function<Throwable, ? extends T> fn)

(3) : 構建依賴

無結果的依賴呼叫: CompletableFuture.runAsync(taskA).thenRun(taskB).thenRun(taskC).join // 非同步版本的是 thenRunAsync

有結果的依賴呼叫: 

// Supplier<String> taskA = () -> "hello";  Function<String, String> taskB = (t) -> t.toUpperCase(); Consumer<String> taskC = (t) -> System.out.println("consume: " + t);

CompletableFuture.supplyAsync(taskA)

    .thenApply(taskB)

    .thenAccept(taskC)

    .join();

runAfterBoth(對應任務Runnable), thenCombine(對應任務BiFunction), thenAcceptBoth(對應任務型別BiConsumer)

Supplier<String> taskA = () -> "taskA";

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

BiFunction<String, String, String> taskC = (a, b) -> a + "," + b;

String ret = CompletableFuture.supplyAsync(taskA)

        .thenCombineAsync(taskB, taskC)

        .join();

其中還有更為神奇的是allOf 和 anyOf, 列子都是來自於老馬程式設計的,寫的著實很好。


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

    delayRandom(100, 1000);

    return "helloA";

}, executor);

CompletableFuture<Void> taskB = CompletableFuture.runAsync(() -> {

    delayRandom(2000, 3000);

}, executor);

CompletableFuture<Void> taskC = CompletableFuture.runAsync(() -> {

    delayRandom(30, 100);

    throw new RuntimeException("task C exception");

}, executor);

CompletableFuture.allOf(taskA, taskB, taskC).whenComplete((result, ex) -> {

    if (ex != null) {

        System.out.println(ex.getMessage());

    }

    if (!taskA.isCompletedExceptionally()) {

        System.out.println("task A " + taskA.join());

    }

});


後面的執行緒使用的是ForkJoinPool,會建立相應的worker:tryAddWorker。裡面的程式碼真的是太底層了,unsafe class 會涉及很多

(4), 我們們再回頭簡單看看 DefaultFuture, FutureAdapter 繼承 completableFuture,futureAdapter中的 future.setCallback,會呼叫預設的DefaultFuture的setcallback。setCallback 會invoke callback:invokeCallback(callback);

其實dubbo 裡面的非同步支援比 2.6.x的非同步程式碼,改變很大,也很精妙。等有時間再說把,先看看CompletableFuture 強大的功能。

相關文章