Java中CompletableFuture的thenApply與thenApplyAsync比較

banq發表於2024-05-29

Java 的 CompletableFuture 類提供了兩種關鍵方法,thenApply 和 thenApplyAsync,用於處理非同步計算的結果。雖然這兩種方法的用途相同,但它們的細微差別可能會顯著影響程式的效能和併發性。

本文探討了 thenApply 和 thenApplyAsync 之間的區別,並根據應用程式的具體要求,提供何時使用每種方法的見解。

`thenApply` 和 `thenApplyAsync` 都是 Java 的 CompletableFuture 類中的方法,它們用於在計算結果可用時對其進行處理。然而,它們之間存在一些關鍵差異,這些差異可能會影響程式的效能和行為。

1.執行執行緒:
`thenApply` 和 `thenApplyAsync` 之間的主要區別在於它們執行的執行緒。

  • `thenApply` 在完成前一個 CompletableFuture 的同一執行緒上執行回撥函式,
  • 而 `thenApplyAsync` 在從 ForkJoinPool.commonPool()(預設)或給定的 Executor 獲取的不同執行緒上執行回撥函式。

2.非阻塞:
`thenApplyAsync` 是非阻塞的。它可以將後續完成階段解除安裝到其他執行緒,同時允許當前執行緒執行其他工作。這對於希望充分利用系統資源的高併發程式非常有用。

3.效能:

  • 當回撥函式是長時間執行的任務時,涉及阻塞操作、I/O 操作或計算密集型任務時,`thenApplyAsync` 會更高效,因為它不會阻塞主執行緒。
  • 但是,如果任務很短且很快,`thenApply` 可能更可取,因為它避免了建立新任務並將其安排在不同執行緒上執行的開銷。

場景案例
讓我們考慮一個現實世界的場景:一個線上購物系統。
在這個系統中,當使用者下訂單時,會發生幾個步驟:

  1. 訂單詳細資訊儲存在資料庫中。
  2. 向使用者傳送電子郵件確認。
  3. 訂單被送往倉庫進行包裝和發貨。

每個步驟都可以用 CompletableFuture 來表示,並且可以使用thenApply或thenApplyAsync將它們連結在一起。

使用方法如下thenApply:

CompletableFuture.supplyAsync(() -> saveOrderToDatabase(order))
    .thenApply(orderId -> sendConfirmationEmail(orderId))
    .thenApply(emailSuccess -> sendOrderToWarehouse(order));


在這種情況下,每個步驟都將在同一執行緒上執行。
如果傳送確認電子郵件(sendConfirmationEmail(orderId))需要很長時間,就會阻塞執行緒直到傳送完成,從而延遲傳送訂單到倉庫(sendOrderToWarehouse(order))操作。

使用thenApplyAsync:

CompletableFuture.supplyAsync(() -> saveOrderToDatabase(order))
    .thenApplyAsync(orderId -> sendConfirmationEmail(orderId))
    .thenApplyAsync(emailSuccess -> sendOrderToWarehouse(order));


在這種情況下,每個步驟都將在不同的執行緒中執行。如果傳送確認電子郵件需要很長時間,則不會阻塞將訂單儲存到資料庫的執行緒。相反,它會被解除安裝到一個單獨的執行緒中,一旦訂單被儲存到資料庫,就可以立即開始 sendOrderToWarehouse(order) 操作。

這是一個簡化的示例,但它說明了 thenApplyAsync 的優勢,在這種情況下,操作可能會長期執行,而你又想避免阻塞執行緒。

當呼叫 thenApply 和 thenApplyAsync 時,它們所鏈到的 CompletableFuture 是否完成?這就是兩者區別所在,詳解如下:

  • 在 thenApply 的情況下,如果 CompletableFuture 已經完成,那麼如果呼叫執行緒尚未開始處理結果(即如果結果處理尚未開始),它將立即在呼叫執行緒上執行回撥。如果回撥是一個長期執行的操作,這可能會阻塞呼叫執行緒。
  • 無論 CompletableFuture 是否已經完成,thenApplyAsync 始終會在 ForkJoinPool(或提供的 Executor)之外的單獨執行緒中執行回撥。這樣,即使回撥是一個長期執行的操作,也能確保呼叫執行緒不會被阻塞。

總之:
因此,如果您有一個回撥,該回撥可能是長時間執行的操作,並且您不想冒險阻塞新增回撥的執行緒,那麼這thenApplyAsync將是有益的。它提供了更一致的非同步行為,確保操作始終被轉移到不同的執行緒。

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

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

另一方面,thenApplyAsync()方法適用於以下情況:

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

兩者主要區別是背後執行緒機制:

  1. thenApply ():執行行為與前一階段相同的執行緒或來自執行器池的單獨執行緒(如果在完成之前呼叫)將執行緒與執行器池分開
  2. thenApplyAsync:將執行緒與執行器池分開,會在預設 ForkJoinPool.commonPool()中重新開啟新執行緒,再次利用 ForkJoin併發高效能。

在本文中,我們探討了CompletableFuture框架中thenApply()和thenApplyAsync()方法之間的功能和差異。
thenApply()可能會阻塞執行緒,因此它適合輕量級轉換或可以接受同步執行的場景。另一方面, thenApplyAsync()保證非同步執行,因此它非常適合涉及潛在阻塞的操作或計算密集型任務,這些任務對響應性至關重要。

相關文章