CompletableFuture 是非阻塞的嗎?
一、概述
擁有高性能和可用性是現代軟件開發的重要組成部分。
實現這一目標的一種方法是通過非阻塞和異步編程。在 Java 中, CompletableFuture
類提供了一種編寫非阻塞代碼的方法。但它真的是非阻塞的嗎?
在本教程中,我們將檢查CompletableFuture
阻塞和非阻塞的情況。
2. CompletableFuture
首先,讓我們簡單了解一下CompletableFuture
類。它是 Java 8 中引入的一個強大的類,作為並發 API 的一部分。
此外,它實現了[Future](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html)
接口並代表了[CompletionStage](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html)
接口的主要實現。因此,它提供了將近 50 種不同的方法來創建和執行異步計算。
為什麼我們首先需要CompletableFurure
?使用Future
接口,我們只能通過調用get()
方法來檢索結果。但是,此方法代表阻塞操作。換句話說,它將阻塞當前線程,直到任務的結果可用。
如果我們需要對結果執行額外的操作,我們將以阻塞操作結束。
另一方面,多虧了CompletionStage
, CompletableFuture
提供了將多個可以並發運行的計算鏈接在一起的能力。此功能允許我們創建一個任務鏈,在當前任務完成時觸發下一個任務。
此外,我們可以在不阻塞當前線程的情況下指定一旦我們從未來獲得結果應該發生什麼。
CompletableFuture
類表示相關流程中的兩個階段,其中一個階段的完成會觸發另一個階段及其結果。
3.阻塞與非阻塞
接下來,我們來了解一下阻塞和非阻塞處理的區別。
在阻塞操作中,調用線程在繼續執行之前等待另一個線程中的操作完成:
在這裡,任務按順序執行。 Thread 1
被Thread 2
阻塞。換句話說,在Thread 2
完成其任務處理之前, Thread 1
無法繼續執行。
我們可以把阻塞處理看作是同步操作。
但是,我們系統中的阻塞操作會導致性能問題,尤其是在需要高可用性和可擴展性的應用程序中。
相反,非阻塞操作允許線程同時執行多個計算,而不必等待每個任務完成。
當前線程可以繼續執行,而其他線程並行執行任務:
在上面的示例中, Thread 2
沒有阻止Thread 1
的執行。此外,兩個線程都同時運行它們的任務。
除了提高性能之外,我們還可以決定在非阻塞操作完成執行後如何處理結果。
4. CompletableFuture
和非阻塞操作
使用CompletableFuture
的主要優點是它能夠將多個任務鏈接在一起,這些任務將在不阻塞當前線程的情況下執行。因此,我們可以說CompletableFuture
是非阻塞的。
此外,它還提供了幾種允許我們以非阻塞方式執行任務的方法,包括:
-
supplyAsync()
:異步執行任務並返回表示結果的CompletableFuture
-
thenApply()
:將函數應用於先前任務的結果並返回表示轉換結果的CompletableFuture
-
thenCompose()
:執行一個返回CompletableFuture
任務,並返回一個表示嵌套任務結果的CompletableFuture
-
allOf()
:並行執行幾個任務,並返回一個CompletableFuture
代表所有任務的完成
接下來,讓我們看一個簡單的例子。例如,假設我們有兩個任務想以非阻塞方式執行:
CompletableFuture.supplyAsync(() -> "Baeldung")
.thenApply(String::length)
.thenAccept(s -> logger.info(String.valueOf(s)));
任務完成後,它將在標準輸出中打印數字8
。
計算在後台運行並返回未來。如果我們有多個依賴動作,則每個動作都由階段表示。一個階段完成後,它會觸發其他依賴階段的計算。
CompletableFuture
什麼時候阻塞?
儘管CompletableFuture
用於執行非阻塞操作,但在某些情況下它仍然會阻塞當前線程。
在異步通信中,我們通常有一個回調機制來檢索計算結果。但是, CompletableFuture
不會在其完成時通知我們。
如果需要,我們可以使用get()
方法在調用線程中檢索結果。
然而,我們需要注意get()
方法使用阻塞處理返回結果。如果需要,它會等待計算完成,然後返回結果。
因此,我們最終會阻塞當前線程,直到 future 完成:
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync(() -> "Baeldung")
.thenApply(String::toUpperCase);
assertEquals("BAELDUNG", completableFuture.get());
同樣,調用join()
方法也會阻塞當前線程:
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync(() -> "Blocking")
.thenApply(s -> s + " Operation")
.thenApply(String::toLowerCase);
assertEquals("blocking operation", completableFuture.join());
這兩種方法之間的主要區別在於,如果 future 異常完成, join()
方法不會拋出已檢查的異常。
此外,我們可以在獲取結果之前調用isDone()
方法來檢查未來是否已完成。
但是,當需要在調用線程中獲取計算結果時,我們可以創建CompletableFuture
,在當前線程中做其他工作,然後調用get()
或join()
方法。通過給它更多的時間, Future
更有可能在我們得到結果之前完成計算。但是仍然不能保證檢索不會最終阻塞線程。
六,結論
在本文中,我們檢查了CompletableFuture
非阻塞和非阻塞的情況。
綜上所述, CompletableFuture
大多數時候是非阻塞的。但是,如果我們調用get()
或join()
方法來檢索結果,它們將阻塞當前線程。
一如既往,整個源代碼都可以在 GitHub 上找到。