Java中CompletableFuture中4種非同步程式設計方法

banq發表於2024-02-25

Java的CompletableFuture框架提供了強大的非同步程式設計能力,方便任務的併發執行。

1、runAsync() 與 SupplyAsync()之間的區別
CompletableFuture是 Java 中一個功能強大的框架,可以實現非同步程式設計,方便同時執行任務而不會阻塞主執行緒。 runAsync()和SupplyAsync()是CompletableFuture類提供的方法。

在進行比較之前,讓我們先了解runAsync()和SupplyAsync()的各個功能。這兩種方法都會啟動非同步任務,使我們能夠併發執行程式碼而不會阻塞主執行緒。

  • runAsync()是一種用於非同步執行不產生結果的任務的方法。它適用於我們想要非同步執行程式碼而不等待結果的“即發即忘”任務。例如,記錄日誌、傳送通知或觸發後臺任務。
  • 另一方面,supplyAsync()是一種用於非同步執行產生結果的任務的方法。它非常適合需要結果進行進一步處理的任務。例如,從資料庫獲取資料、進行 API 呼叫或非同步執行計算。

runAsync()和SupplyAsync()之間的主要區別在於它們接受的輸入和它們生成的返回值的型別。

runAsync()
當要執行的非同步任務沒有產生任何結果時,使用runAsync ()方法。它接受Runnable功能介面並非同步啟動任務。它返回CompletableFuture<Void>,對於重點是完成任務而不是獲取特定結果的場景非常有用。

這是展示其用法的片段:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    <font>// Perform non-result producing task<i>
    System.out.println(
"Task executed asynchronously");
});

在此示例中,runAsync()方法用於非同步執行不產生結果的任務。提供的 lambda 表示式封裝了要執行的任務。完成後,列印:
Task completed successfully

SupplyAsync()
另一方面,當非同步任務產生結果時,將使用SupplyAsync() 。它接受供應商功能介面並非同步啟動任務。隨後,它返回一個CompletableFuture<T>,其中T是任務生成的結果的型別。

讓我們用一個例子來說明這一點:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    <font>// Perform result-producing task<i>
    return
"Result of the asynchronous computation";
});
//稍後獲取結果<i>
String result = future.get();
System.out.println(
"Result: " + result);

在此示例中,supplyAsync()用於非同步執行結果生成任務。SupplyAsync()中的 lambda 表示式表示非同步計算結果的任務。完成後,列印得到的結果:
Result: Result of the asynchronous computation

兩者執行區別:

  • 使用runAsync()時,任務會立即在公共執行緒池中啟動。它的行為反映了呼叫新的Thread(runnable).start()。這意味著任務在呼叫後立即開始執行,沒有任何延遲或排程考慮。
  • supplyAsync()在公共執行緒池中排程任務,如果其他任務排隊,則可能會延遲其執行。這種排程方法有利於資源管理,因為它有助於防止執行緒建立的突然爆發。透過對任務進行排隊並根據執行緒的可用性排程其執行,supplyAsync()可確保高效的資源利用。

2、thenApply() 和 thenApplyAsync() 之間的區別
在CompletableFuture框架中,thenApply()和thenApplyAsync()是促進非同步程式設計的關鍵方法。

CompletableFuture提供了thenApply()和thenApplyAsync()方法,用於將轉換應用於計算結果。這兩種方法都可以對CompletableFuture的結果執行連結操作。

  • thenApply()是一種用於在CompletableFuture完成時將函式應用於其結果的方法。它接受Function函式式介面,將該函式應用於結果,並返回一個帶有轉換結果的新CompletableFuture 。
  • thenApplyAsync()是非同步執行所提供函式的方法。它接受一個Function功能介面和一個可選的Executor,並返回一個新的CompletableFuture和非同步轉換的結果。

thenApply()和thenApplyAsync()之間的主要區別在於它們的執行行為。

thenApply()
預設情況下,thenApply()方法使用完成當前CompletableFuture 的同一執行緒執行轉換函式。這意味著轉換函式的執行可以在結果可用後立即發生。如果轉換函式長時間執行或佔用資源,這可能會阻塞執行緒。

但是,如果我們在尚未完成的CompletableFuture上呼叫thenApply() ,它將在執行程式池的另一個執行緒中非同步執行轉換函式。

這是說明thenApply() 的程式碼片段:

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> thenApplyResultFuture = future.thenApply(num -> <font>"Result: " + num);
String thenApplyResult = thenApplyResultFuture.join();
assertEquals(
"Result: 5", thenApplyResult);

在此示例中,如果結果已經可用並且當前執行緒相容,則thenApply()可能會同步執行該函式。但是,需要注意的是,CompletableFuture會根據各種因素(例如結果的可用性和執行緒上下文)智慧地決定是同步執行還是非同步執行。


thenApply ()方法在以下場景中特別有用:

  • 順序轉換:當需要對CompletableFuture的結果順序應用轉換時。這可能涉及將數字結果轉換為字串或根據結果執行計算等任務。
  • 輕量級操作:它非常適合執行小型、快速的轉換,不會對呼叫執行緒造成明顯的阻塞。示例包括將數字轉換為字串、根據結果執行計算或運算元據結構。


thenApplyAsync()
相比之下,thenApplyAsync()透過利用執行器池(通常是ForkJoinPool.commonPool() )中的執行緒來保證所提供函式的非同步執行。這確保了該函式是非同步執行的,並且可以在單獨的執行緒中執行,從而防止當前執行緒的任何阻塞。

以下是我們如何使用thenApplyAsync():

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> thenApplyAsyncResultFuture = future.thenApplyAsync(num -> <font>"Result: " + num);
String thenApplyAsyncResult = thenApplyAsyncResultFuture.join();
assertEquals(
"Result: 5", thenApplyAsyncResult);

在此示例中,即使結果立即可用,  thenApplyAsync() 也始終安排該函式在單獨的執行緒上非同步執行。

thenApplyAsync()方法適用於以下情況:

  • 非同步轉換:當需要非同步應用轉換時,可能會利用多個執行緒並行執行。例如,在使用者上傳影像進行編輯的 Web 應用程式中,使用CompletableFuture進行非同步轉換有利於同時應用調整大小、濾鏡和水印,從而提高處理效率和使用者體驗。
  • 阻塞操作:如果轉換函式涉及阻塞操作、I/O 操作或計算密集型任務,則thenApplyAsync()會變得有利。透過將此類計算解除安裝到單獨的執行緒,有助於防止阻塞呼叫執行緒,從而確保更流暢的應用程式效能。

以上方法鏈式操作
runAsync()方法
runAsync()
方法不能直接與thenApply()或thenAccept()等方法連結,因為它不產生結果。但是,我們可以在runAsync()任務完成後使用thenRun()執行另一個任務。此方法允許我們連結附加任務以順序執行,而不依賴於初始任務的結果。

下面的示例展示了使用runAsync()和thenRun()進行連結操作:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println(<font>"Task executed asynchronously");
});
future.thenRun(() -> {
   
// 在 runAsync() 完成後執行另一個任務<i>
    System.out.println(
"Another task executed after runAsync() completes");
});

在此示例中,我們首先使用runAsync()非同步執行任務。然後,我們使用thenRun()指定在初始任務完成後要執行的另一個任務。這允許我們按順序連結多個任務,從而產生以下輸出:

Task executed asynchronously
Another task executed after runAsync() completes

supplyAsync()方法
相反,supplyAsync()由於其返回值而允許連結操作。由於SupplyAsync()會生成結果,因此我們可以使用thenApply()等方法來轉換結果,使用thenAccept()來使用結果,或者使用thenCompose()來連結進一步的非同步操作。這種靈活性使我們能夠透過將多個任務連結在一起來構建複雜的非同步工作流程。

下面的示例說明了SupplyAsync()和thenApply()的連結操作:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return <font>"Result of the asynchronous computation";
});
future.thenApply(result -> {
   
// Transform the result<i>
    return result.toUpperCase();
}).thenAccept(transformedResult -> {
   
// Consume the transformed result<i>
    System.out.println(
"Transformed Result: " + transformedResult);
});

在此示例中,我們首先使用SupplyAsync()非同步執行任務,這會生成結果。然後,我們使用thenApply()來轉換結果,然後使用thenAccept()來使用轉換後的結果。這演示了使用SupplyAsync()連結多個操作,從而允許更復雜的非同步工作流程。

下面是輸出的示例:

Transformed Result: RESULT OF THE ASYNCHRONOUS COMPUTATION

 

相關文章