簡體   English   中英

Java CompletableFuture 的 thenApply 和 thenApplyAsync 有什么區別?

[英]What is the difference between thenApply and thenApplyAsync of Java CompletableFuture?

假設我有以下代碼:

CompletableFuture<Integer> future  
        = CompletableFuture.supplyAsync( () -> 0);

thenApply案例:

future.thenApply( x -> x + 1 )
      .thenApply( x -> x + 1 )
      .thenAccept( x -> System.out.println(x));

這里 output 將為 2。現在在thenApplyAsync的情況下:

future.thenApplyAsync( x -> x + 1 )   // first step
      .thenApplyAsync( x -> x + 1 )   // second step
      .thenAccept( x -> System.out.println(x)); // third step

我在這篇博客中讀到,每個thenApplyAsync都在一個單獨的線程中執行並且“同時”(這意味着在thenApplyAsyncs完成之前開始的thenApplyAsyncs之后),如果是這樣,如果是第一步,那么第二步的輸入參數值是多少沒做完?

如果不采取第二步,第一步 go 的結果將在哪里? 第三步將采取哪一步的結果?

如果第二步必須等待第一步的結果,那么Async的意義何在?

這里 x -> x + 1 只是為了說明這一點,我想知道的是在計算時間很長的情況下。

區別在於負責運行代碼的Executor CompletableFuture上的每個算子通常有 3 個版本。

  1. thenApply(fn) - 在調用它的CompleteableFuture定義的線程上運行fn ,所以你通常不知道這將在哪里執行。 如果結果已經可用,它可能會立即執行。
  2. thenApplyAsync(fn) - 無論情況如何,都在環境定義的執行器上運行fn 對於CompletableFuture這通常是ForkJoinPool.commonPool()
  3. thenApplyAsync(fn,exec) - 在exec上運行fn

最終結果是一樣的,但調度行為取決於方法的選擇。

你錯誤地引用了文章的例子,所以你錯誤地應用了文章的結論。 我在你的問題中看到兩個問題:

.then___()的正確用法是什么

在您引用的兩個示例中(文章中沒有),第二個函數必須等待第一個函數完成。 每當你調用a.then___(b -> ...)輸入b的結果是a和必須等待a來完成,不管你是否使用命名方法Async與否。 這篇文章的結論不適用,因為你錯誤地引用了它。

文章中的例子其實是

CompletableFuture<String> receiver = CompletableFuture.supplyAsync(this::findReceiver);

receiver.thenApplyAsync(this::sendMsg);  
receiver.thenApplyAsync(this::sendMsg);  

請注意thenApplyAsync都應用於receiver ,而不是鏈接在同一個語句中。 這意味着一旦receiver完成,兩個函數都可以以未指定的順序啟動。 (任何順序假設都取決於實現。)

更清楚地說:

a.thenApply(b).thenApply(c); 表示順序是a完成然后b開始, b完成,然后c開始。
a.thenApplyAsync(b).thenApplyAsync(c); a b c之間的排序而言, a行為與上述完全相同。

a.thenApply(b); a.thenApply(c); 表示a結束,然后bc可以以任何順序開始。 bc不必互相等待。
a.thenApplyAync(b); a.thenApplyAsync(c); 就順序而言,工作方式相同。

在閱讀以下內容之前,您應該了解以上內容。 以上涉及異步編程,沒有它您將無法正確使用 API。 下面涉及線程管理,您可以通過它優化程序並避免性能缺陷。 但是如果不正確編寫程序,就無法優化程序。


如題:Java CompletableFuture 的thenApplythenApplyAsync的區別?

我必須指出,編寫 JSR 的人一定混淆了技術術語“異步編程”,並選擇了現在讓新手和老手都感到困惑的名稱。 首先, thenApplyAsync中沒有任何東西比這些方法的契約中的thenApply更異步。

兩者之間的區別與函數在哪個線程上運行有關。 提供給thenApply的函數可以在任何線程上運行

  1. 通話complete
  2. 在同一實例上調用thenApply

thenApplyAsync的 2 個重載要么

  1. 使用默認的Executor (又名線程池),或
  2. 使用提供的Executor

需要注意的是,對於thenApply ,運行時承諾最終使用一些您無法控制的執行程序運行您的函數。 如果您想控制線程,請使用 Async 變體。

如果您的函數是輕量級的,那么哪個線程運行您的函數並不重要。

如果您的函數受到大量 CPU 限制,您不希望將它留給運行時。 如果運行時選擇網絡線程來運行您的函數,則網絡線程無法花時間處理網絡請求,從而導致網絡請求在隊列中等待更長時間,並且您的服務器變得無響應。 在這種情況下,您希望將thenApplyAsync與您自己的線程池一起使用。


有趣的事實:異步 != 線程

thenApply / thenApplyAsync ,以及它們的對應物thenCompose / thenComposeAsynchandle / handleAsyncthenAccept / thenAcceptAsync ,都是異步的! 這些函數的異步特性與異步操作最終調用completecompleteExceptionally的事實有關。 這個想法來自 Javascript,它確實是異步的,但不是多線程的。

這是文檔中關於CompletableFuture's thenApplyAsync

返回一個新的 CompletionStage,當這個階段正常完成時,使用這個階段的默認異步執行工具執行,這個階段的結果作為提供的函數的參數。

所以, thenApplyAsync必須等待上一個thenApplyAsync's結果:

在您的情況下,您首先進行同步工作,然后進行異步工作。 因此,第二個是異步的並不重要,因為它只有在同步工作完成后才啟動。

讓我們切換它。 在某些情況下,“async result: 2”將首先打印,而在某些情況下,“sync result: 2”將首先打印。 這里有區別,因為調用 1 和 2 都可以異步運行,在單獨的線程上調用 1 並在其他線程上調用 2,該線程可能是主線程。

CompletableFuture<Integer> future
                = CompletableFuture.supplyAsync(() -> 0);

future.thenApplyAsync(x -> x + 1) // call 1
                .thenApplyAsync(x -> x + 1)
                .thenAccept(x -> System.out.println("async result: " + x));

future.thenApply(x -> x + 1) // call 2
                .thenApply(x -> x + 1)
                .thenAccept(x -> System.out.println("sync result:" + x));

第二步(即計算)將始終在第一步之后執行。

如果第二步必須等待第一步的結果,那么 Async 的意義何在?

在這種情況下,異步意味着您可以保證該方法將快速返回並且計算將在不同的線程中執行。

當調用thenApply (沒有異步)時,你就沒有這樣的保證。 在這種情況下,如果 CompletableFuture 在調用方法時已經完成,則計算可以同步執行,即在調用thenApply的同一線程中。 但是計算也可以由完成 future 的線程或調用同一CompletableFuture上的方法的某個其他線程異步執行。 這個答案: https ://stackoverflow.com/a/46062939/1235217 詳細解釋了 thenApply 做什么和不保證什么。

那么什么時候應該使用thenApply ,什么時候使用thenApplyAsync呢? 我使用以下經驗法則:

  • 非異步:僅當任務非常小且非阻塞時,因為在這種情況下我們不關心哪個可能的線程執行它
  • 異步(通常以顯式執行器作為參數):用於所有其他任務

thenApplyAsyncthenApply Consumer<? super T> action Consumer<? super T> action將被異步調用,並且不會阻塞指定消費者的線程。

區別在於哪個線程將負責調用方法Consumer#accept(T t)

考慮如下AsyncHttpClient調用:注意下面打印的線程名稱。 我希望它能讓您清楚區別:

// running in the main method
// public static void main(String[] args) ....

CompletableFuture<Response> future =
    asyncHttpClient.prepareGet(uri).execute().toCompletableFuture();

log.info("Current Thread " + Thread.currentThread().getName());

//Prints "Current Thread main"

thenApply將使用完成未來的同一線程。

//will use the dispatcher threads from the asyncHttpClient to call `consumer.apply`
//The thread that completed the future will be blocked by the execution of the method `Consumer#accept(T t)`.
future.thenApply(myResult -> {
    log.info("Applier Thread " + Thread.currentThread().getName());
    return myResult;
})

//Prints: "Applier Thread httpclient-dispatch-8"

thenApplyAsync將使用 Executor 池中的線程。

//will use the threads from the CommonPool to call `consumer.accept`
//The thread that completed the future WON'T be blocked by the execution of the method `Consumer#accept(T t)`.
future.thenApplyAsync(myResult -> {
    log.info("Applier Thread " + Thread.currentThread().getName());
    return myResult;
})

//Prints: "Applier Thread ForkJoinPool.commonPool-worker-7"

future.get()會阻塞主線程。

//If called, `.get()` may block the main thread if the CompletableFuture is not completed.
future.get();

結論

thenApplyAsync方法中的Async后綴表示完成未來的線程不會被Consumer#accept(T t) method的執行阻塞。

thenApplyAsyncthenApply的用法取決於您是否要阻止線程完成未來。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM