java8-[CompletableFuture]

Future代表異步執行的掛起的結果。它的方法get,在執行計算後返回執行結果。由此產生問題:get方法調用是阻塞的,直到異步計算結束。由此造成異步計算起不到“異步”的效果。你可以在job中實現所有場景,再發送到executor中,但是爲什麼要爲錯綜複雜的邏輯編寫同樣錯綜複雜的執行步驟呢。因此,你需要CompletableFuture

CompletableFuture

實現Future接口外,它還實現了CompletionStage接口,CompletionStage接口保證了計算最終會被執行。CompletionStage的最大優點是,提供了多種方法,用於執行結束時回調(callbacks)。由此構建非阻塞的系統。

最簡單的異步計算

創建異步計算:
CompletableFuture.supplyAsync(this::sendMsg);
supplyAsync方法有一個Supplier類作爲參數,該類包含了需要異步執行的代碼,這個例子中是sendMsg方法。像Future一樣,可以提供Executor作爲第二個參數,如果缺省,Supplier會被提交給ForkJoinPool.commonPool()

添加回調

回調的意義是:當異步計算結束時,執行回調的代碼,而不用一直等待執行結束。上例中,通過sendMsg方法異步發送一個消息,接下來,我們添加回調代碼,用於通知發送消息執行的結果。

CompletableFuture.supplyAsync(this::sendMsg).thenAccept(this::notify);

thenAccept是諸多添加回調的一種方法。參數是Consumer,本例中是notify,用於計算執行結束時處理結果。

級聯多個回調

如果想持續的在多個回調之間傳遞值,thenAccept不能用,因爲Consumer不返回任何東西。一直傳值,可以用thenApply
thenApply的參數Function,接受一個值,並且返回一個值。

CompletableFuture.supplyAsync(this::findReceiver).thenApply(this:sendMsg).thenAccept(this::notify);

現在異步的任務是,首先找到接收者,然後發送消息到接收器,最後發送消息的結果發給notify。

構建異步系統

構建更大的異步系統,你很可能想創建新的代碼段,依賴小的代碼段。這些大小代碼段都是異步的,在我們的例子中返回CompletionStage。到目前爲止,sendMsg都是一個阻塞函數,假設有個返回CompletionStage的sendMsgAsync函數,如果像上面例子一直使用thenApply,最終我們會得到一堆嵌套的CompletionStage。

CompletableFuture.supplyAsync(this::findReceiver).thenApply(this::sendMsgAsnc);
// returns type: CompletionStage<CompletionStage<String>>

而我們不想要嵌套的CompletionStage,所以我們可以用thenCompose,它接受Function參數,返回CompletionStage。就像flatMap實現的扁平效果一樣:

CompletableFuture.supplyAsync(this::findReceiver).thenCompose(this::sendMsgAsync);
// returns type CompletionStage<String>

用async結尾的回調分離的任務

到目前爲止,所有的回調都在它前一個執行單元的線程中執行。可以把回調各自提交到ForkJoinPool.commonPool(),而不是使用前一個執行單元的線程,這是通過使用CompletionStage提供的以async後綴的方法實現的。
發送兩個報文到相同的接收器:

CompletableFuture<String> reciever = CompletableFuture.supplyAsync(this::findReceiver);
reciever.thenApply(this::sendMsg);
reciever.thenApply(this::sendOtherMsg);

上面例子中,所有的Function都在同一個線程中執行,導致後一個消息等待前一個消息執行完。

CompletableFuture<String> reciever = CompletableFuture.supplyAsync(this::findReciever);
receiver.thenApplyAsync(this::sendMsg);
reciever.thenApplyAsync(this::sendMsg);

上面的例子,每個發送消息都被獨立的提交給ForkJoinPool.commonPool()。結果是兩個sendMsg回調在異步計算執行結束時都會執行。
關鍵是:異步版本的方法,在你有幾個回調函數,而它們又依賴相同計算結果的時,非常方便有效。

異常的處理

如果你用過Future,就會知道糟糕的時候有多糟糕。幸運的是,CompletableFuture有一個漂亮的對應手段,通過使用exceptionally

CompletableFuture.supplyAsync(this::failingMsg).exceptionally(ex->new Result(Status.FAILED)).thenAccept(this::notify);

exceptionally給我們一個機會恢復,通過執行當異步執行的計算拋出exception時備選的方法(alternative method)。而其他的回調可以繼續執行,它們的輸入是alternative method的返回值。更復雜的差錯處理,可以用whenCompletehandle

基於多個計算結果的回調

創建依賴多個計算結果的回調,用thenCombine,它允許註冊BiFunction類回調,依賴兩個CompletionStage

CompletableFuture<String> to = CompletableFuture.supplyAsync(this::findReceiver);
CompletableFuture<String> text = CompletableFuture.supplyAsync(this::createContent);
to.thenCombine(text, this::sendMsg);

值得一提的是,thenCombine有一個變種,runAfterBoth,它不關心前面的計算結果,只要它們執行完就夠了,它有一個Runnable參數。

依賴於至少一個計算結果的回調

發送消息有兩個消息源,任何一個源執行完畢就可以發送消息。

CompletableFuture<String> firstSource = CompletableFuture.supplyAsync(this::findByFirstSource);
CompletableFuture<String> secondSource = CompletableFuture.supplyAsync(this::findBySecondSource);
firstSource.acceptEither(secondSource, this::sendMsg);

通過acceptEither實現,它接受兩個等待中的計算,以及一個Consumer,後者在兩個計算中任意一個執行結束後執行。

例子

@Scheduled(fixedRate = 2000)
public void test() {
    log.info("begin: {}", new Date());
    ActionService actionService = new ActionService();
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(actionService::test1)
        .thenApply(actionService::test2)
        .thenApply(actionService::test3)
        .thenAccept(actionService::test4);
    try {
        future.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

    log.info("end: {}", new Date());
}

public Integer test1() {
    log.info("{}, {}", Thread.currentThread().getName(), new Date());
    try {
        Thread.sleep(1000l);
    } catch (InterruptedException e) {
        log.error(e.getMessage());
    }
    return 1;
}

運行結果:
執行結果

CompletableFuture的更多用法

document

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