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的返回值。更復雜的差錯處理,可以用whenComplete
和handle
。
基於多個計算結果的回調
創建依賴多個計算結果的回調,用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;
}
運行結果: