web-dev-qa-db-ja.com

Java CompletableFutureのthenApplyとthenApplyAsyncの違いは何ですか?

次のコードがあるとします:

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

thenApplyケース:

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

ここで、出力は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が開始されることを意味します] _ finish)、そうであれば、最初のステップが終了していない場合の2番目のステップの入力引数値は何ですか?

2番目のステップで行われなかった場合、最初のステップの結果はどこに行きますか? 3番目のステップはどのステップの結果を取りますか?

2番目のステップが最初のステップの結果を待つ必要がある場合、Asyncのポイントは何ですか?

ここでx-> x + 1はポイントを示すためだけのもので、非常に長い計算の場合に知りたいことです。

30
Yulin

違いは、コードの実行を担当するExecutorに関係しています。 CompletableFutureの各演算子には、通常3つのバージョンがあります。

  1. thenApply(fn)-fnによって定義されたスレッドでCompleteableFutureを実行します。そのため、これが実行される場所を一般に知ることができません。結果がすでに利用可能な場合、すぐに実行される場合があります。
  2. thenApplyAsync(fn)-状況に関係なく、環境定義のエグゼキューターでfnを実行します。 CompletableFutureの場合、これは通常ForkJoinPool.commonPool()になります。
  3. thenApplyAsync(fn,exec)-fnexecを実行します。

最終的に結果は同じですが、スケジューリングの動作は方法の選択に依存します。

24
Kiskae

名前thenApplythenApplyAsyncは絶対に恐ろしく、紛らわしいことを指摘しなければなりません。 thenApplyAsyncには、これらのメソッドのコントラクトからのthenApplyよりも非同期なものはありません。

違いは、関数が実行されるスレッドと関係があります。 thenApplyに提供される関数 どのスレッドでも実行可能

  1. completeを呼び出します
  2. 同じインスタンスでthenApplyを呼び出します

一方、thenApplyAsyncはデフォルトのExecutor(別名スレッドプール)を使用するか、指定されたExecutorを使用します。

これらの関数の非同期部分は、非同期操作が最終的にcompleteまたはcompleteExceptionallyを呼び出すという事実に関係しています。このアイデアは、マルチスレッドとは関係のないJavascriptから生まれました。

4
1283822

これは、ドキュメントがCompletableFuture'sthenApplyAsync について述べていることです:

このステージが正常に完了すると、このステージのデフォルトの非同期実行機能を使用して、このステージの結果が提供された関数の引数として実行される新しいCompletionStageを返します。

したがって、thenApplyAsyncは、前のthenApplyAsync's結果を待つ必要があります。

あなたの場合、最初に同期作業を行い、次に非同期作業を行います。したがって、同期作業が終了した後にのみ開始されるため、2番目のものが非同期であることは重要ではありません。

切り替えましょう。場合によっては「非同期結果:2」が最初に印刷され、場合によっては「同期結果: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));
1
Willi Mentzel

2番目のステップ(つまり、計算)は常に最初のステップの後に実行されます。

2番目のステップが最初のステップの結果を待つ必要がある場合、非同期のポイントは何ですか?

この場合、非同期とは、メソッドが迅速に戻り、計算が別のスレッドで実行されることが保証されることを意味します。

thenApply(非同期なし)を呼び出す場合、そのような保証はありません。この場合、CompletableFutureがメソッドが呼び出されるまでに既に完了している場合、thenApplyを呼び出す同じスレッドで同期的に実行できます。ただし、計算は、未来を完了するスレッドまたは同じCompletableFutureでメソッドを呼び出す他のスレッドによって非同期に実行される場合もあります。この回答: https://stackoverflow.com/a/46062939/1235217 thenApplyが何を保証し、何を保証しないかを詳細に説明しました。

それで、いつthenApplythenApplyAsyncを使うべきですか?私は次の経験則を使用します。

  • 非非同期:タスクが非常に小さく、ブロックされていない場合のみ。この場合、どのスレッドがそれを実行するかを気にしないため
  • async(多くの場合、パラメーターとして明示的なexecutorを使用):他のすべてのタスク
0