在 Java 併發程式設計中,傳統的執行緒和同步機制如Thread
類和Runnable
介面提供了基本的並行執行能力,但它們的使用往往需要編寫大量的樣板程式碼來處理執行緒的建立、管理和同步,從而導致程式碼複雜且難以維護。為了解決這些問題,Java 5 引入了java.util.concurrent
包,提供瞭如ExecutorService
和Future
等高階抽象來簡化併發程式設計。然而,Future
介面在處理非同步任務時仍然存在一些侷限,例如無法方便地處理回撥、組合多個任務以及處理異常。
為了解決這些問題,Java 8 引入了CompletableFuture
,它不僅實現了Future
介面,還提供了豐富的 API 來支援非同步程式設計。透過CompletableFuture
,開發者可以更優雅地處理非同步任務的執行、結果處理和異常處理。CompletableFuture
提供了諸如thenApply
、thenAccept
、thenCombine
等方法,可以輕鬆地將多個非同步任務串聯或並行執行,並在任務完成後進行回撥處理。此外,CompletableFuture
還支援自定義執行緒池,使得開發者可以靈活地管理執行緒資源,提高程式的併發效能和可維護性。
CompletableFuture
的引入極大地簡化了 Java 併發程式設計,提供了一種更直觀、更強大的方式來編寫非同步和並行程式碼,使得複雜的併發任務變得更加易於實現和維護。
功能
CompletableFuture
專注於非同步任務的結果,並提供豐富的 API 用於組合和錯誤處理。它負責:
- 並行處理:可以將多個獨立的任務並行執行,然後合併結果。
- 非同步回撥:可以在任務完成後執行回撥函式,而不阻塞主執行緒。
- 異常處理:在非同步操作中更方便地處理異常情況。
程式碼示例
以下程式碼演示了在 Java 中使用來CompletableFuture
處理非同步計算。
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello,FunTester! " + Thread.currentThread().getName());
return "Hello,FunTester!";
});
future.thenAccept(System.out::println);
future.join();
}
這個示例程式碼展示瞭如何使用 Java 的 CompletableFuture 類來非同步執行任務,並處理任務的結果。讓我們逐步解析一下:
-
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {...});
這一行建立了一個 CompletableFuture 例項,並使用supplyAsync
方法非同步執行提供的 lambda 表示式。lambda 表示式的程式碼塊中,首先列印了一個字串和當前執行緒名稱,然後返回字串"Hello,FunTester!"
。 -
future.thenAccept(System.out::println);
這一行註冊了一個回撥函式,當上一步非同步任務完成時,它會將任務的結果 (即字串"Hello,FunTester!"
傳遞給System.out::println
方法,從而將其列印到控制檯。 -
future.join();
這一行是一個阻塞操作,它會等待非同步任務完成。如果非同步任務已經完成,則立即返回;否則,它會一直等待直到非同步任務完成。
因此,執行這個程式時,它會先列印"Hello,FunTester! [執行緒名稱]"
(這是在非同步任務中列印的),然後列印"Hello,FunTester!"
(這是由thenAccept
回撥列印的)。
這個示例展示了 CompletableFuture 如何簡化非同步程式設計。你可以使用 lambda 表示式來定義非同步任務,並使用thenAccept
等方法來註冊對任務結果的處理邏輯。CompletableFuture 還提供了其他有用的方法,如thenApply
、thenCompose
等,用於組合和鏈式執行多個非同步任務。
鏈式非同步任務
CompletableFuture
的強大功能之一就是能夠將多個非同步任務連結在一起。處理複雜的非同步工作流時,這可以使程式碼更具可讀性和可維護性。
以下程式碼演示瞭如何CompletableFuture
在 Java 中使用連結多個任務來建立一系列非同步計算。
import java.util.concurrent.CompletableFuture;
public class ChainingTasksExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Task 1")
.thenApply(result -> result + " + Task 2")
.thenApply(result -> result + " + Task 3")
.thenAccept(System.out::println);
}
}
這個案例中展示了 CompletableFuture 的鏈式呼叫和結果轉換的用法。讓我們逐步解析一下:
-
CompletableFuture.supplyAsync(() -> "Task 1")
- 這一行建立了一個 CompletableFuture 例項,並使用
supplyAsync
方法非同步執行一個 lambda 表示式,該表示式返回字串"Task 1"
。
- 這一行建立了一個 CompletableFuture 例項,並使用
-
.thenApply(result -> result + " + Task 2")
-
thenApply
方法接受一個函式式介面Function
作為引數,該函式接收上一個任務的結果作為輸入,並返回一個新的結果。 - 在這裡,lambda 表示式
result -> result + " + Task 2"
將上一個任務的結果 ("Task 1"
) 與字串" + Task 2"
連線,返回"Task 1 + Task 2"
。
-
-
.thenApply(result -> result + " + Task 3")
- 這一行又使用
thenApply
方法,將上一個任務的結果 ("Task 1 + Task 2"
) 與字串" + Task 3"
連線,返回"Task 1 + Task 2 + Task 3"
。
- 這一行又使用
-
.thenAccept(System.out::println);
-
thenAccept
方法接受一個函式式介面Consumer
作為引數,該介面消費上一個任務的結果,但不返回任何值。 - 在這裡,使用
System.out::println
方法引用作為Consumer
的實現,它將列印上一個任務的結果 ("Task 1 + Task 2 + Task 3"
)。
-
因此,當你執行這個程式碼時,它會非同步執行三個任務,每個任務在上一個任務的結果上追加一個字串。最終,它會將最終的結果"Task 1 + Task 2 + Task 3"
列印到控制檯。
這個示例展示了 CompletableFuture 如何透過鏈式呼叫和結果轉換來組合多個非同步任務。每個thenApply
方法都會在上一個任務完成後非同步執行,並將結果傳遞給下一個任務。最後,thenAccept
方法用於消費最終的結果
錯誤處理
CompletableFuture
提供了多種方法來處理非同步任務執行過程中發生的異常。您可以使用exceptionally
、handle
和等方法whenComplete
來妥善處理錯誤。
以下程式碼演示了在使用CompletableFuture
Java 時如何正確處理錯誤。
import java.util.concurrent.CompletableFuture;
public class ErrorHandlingExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Something went wrong!");
return "Success";
}).exceptionally(ex -> {
System.out.println("Error: " + ex.getMessage());
return "Fallback result";
}).thenAccept(System.out::println);
}
}
超時管理
在非同步程式設計中,管理超時至關重要,以避免無限期地等待任務完成。提供和CompletableFuture
等方法來有效地處理超時。
以下程式碼演示瞭如何CompletableFuture
在 Java 中管理超時。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class TimeoutManagementExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result after delay";
}).orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> "Timeout occurred")
.thenAccept(System.out::println);
}
}
這個例子演示瞭如何使用CompletableFuture
的orTimeout
方法來設定非同步任務的超時時間,以及如何在超時發生時進行處理。讓我們逐步分析一下:
-
CompletableFuture.supplyAsync(() -> { ... })
- 這一行建立了一個 CompletableFuture 例項,並使用
supplyAsync
方法非同步執行提供的 lambda 表示式。 - 在該 lambda 表示式中,程式碼呼叫
TimeUnit.SECONDS.sleep(5)
故意讓任務休眠 5 秒鐘,模擬一個耗時操作。
- 這一行建立了一個 CompletableFuture 例項,並使用
-
.orTimeout(2, TimeUnit.SECONDS)
-
orTimeout
方法設定了非同步任務的超時時間為 2 秒。如果任務在 2 秒內未完成,則會觸發超時並返回一個TimeoutException
。
-
-
.exceptionally(ex -> "Timeout occurred")
-
exceptionally
方法接受一個函式式介面Function
作為引數,該函式接收非同步任務丟擲的異常作為輸入,並返回一個備用結果。 - 在這裡,lambda 表示式
ex -> "Timeout occurred"
接收到異常例項ex
後,返回字串"Timeout occurred"
作為備用結果。
-
-
.thenAccept(System.out::println);
-
thenAccept
方法接受一個函式式介面Consumer
作為引數,該介面消費上一個任務的結果,但不返回任何值。 - 在這裡,使用
System.out::println
方法引用作為Consumer
的實現,它將列印上一個任務的結果 (即備用結果"Timeout occurred"
或成功結果"Result after delay"
(如果任務在 2 秒內完成))。
-
當我們執行這個程式時,由於非同步任務會休眠 5 秒鐘,而超時時間設定為 2 秒鐘,因此會觸發超時。exceptionally
方法會被呼叫,並返回備用結果"Timeout occurred"
給thenAccept
方法,最終被列印到控制檯。
輸出應該是:
Timeout occurred
如果將超時時間設定為大於 5 秒,例如orTimeout(6, TimeUnit.SECONDS)
,那麼輸出將是:
Result after delay
這個示例展示瞭如何使用orTimeout
方法來設定 CompletableFuture 的超時時間,以及如何使用exceptionally
方法來處理超時情況。在一些需要控制任務執行時間的場景中,這個功能非常有用,可以防止任務無限期地阻塞或佔用資源。
結論
JavaExecutorService
和CompletableFuture
是管理現代應用程式中併發性的強大工具。它們透過提供易於使用的任務管理、連結、錯誤處理和超時管理 API 來簡化非同步程式設計的複雜性。透過理解和利用這些實用程式,開發人員可以編寫高效、響應迅速且易於維護的併發應用程式。
- 2021 年原創合集
- 2022 年原創合集
- 2023 年原創合集
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go、Python
- 單元&白盒&工具合集
- 測試方案&BUG&爬蟲&UI 自動化
- 測試理論雞湯
- 社群風采&影片合集