web-dev-qa-db-ja.com

CompletableFutureの完了ハンドラーはどのスレッドで実行されますか?

CompletableFutureメソッドについて質問があります。

public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)

事はJavaDocがちょうどこれを言うことです:

このステージが正常に完了すると、指定された関数の引数としてこのステージの結果を使用して実行される新しいCompletionStageを返します。例外的な完了に関する規則については、CompletionStageのドキュメントを参照してください。

スレッドについてはどうですか?これはどのスレッドで実行されますか?スレッドプールによって未来が完成したらどうなるでしょうか。

23
St.Antario

CompletableFuture docsで指定されているポリシーは、理解を深めるのに役立ちます。

  • 非非同期メソッドの依存完了に提供されるアクションは、現在のCompletableFutureを完了するスレッドまたは完了メソッドの他の呼び出し元によって実行できます。

  • 明示的なExecutor引数のないすべての非同期メソッドは、ForkJoinPool.commonPool()を使用して実行されます(ただし、少なくとも2つの並列処理レベルをサポートしていない場合は、各スレッドを実行するために新しいスレッドが作成されます))。監視、デバッグ、追跡を簡略化するために、生成されたすべての非同期タスクはマーカーインターフェイスのインスタンスですCompletableFuture.AsynchronousCompletionTask

pdate:興味深い分析として、@ Mikeが this answer を読むことについてもアドバイスしますドキュメントの詳細に。

21
Naman

@ nullpointer が指摘しているように、ドキュメントには、知っておくべきことが記載されています。ただし、関連するテキストは驚くほどあいまいであり、ここに投稿されたコメント(および回答)の一部は、ドキュメントでサポートされていない仮定に依存しているようです。したがって、私はそれをバラバラにする価値があると思います。具体的には、この段落を非常に注意深く読む必要があります。

非非同期メソッドの依存する完了に提供されるアクションは、現在のCompletableFutureを完了するスレッド、または完了メソッドの他の呼び出し元によって実行できます。

単純明快に聞こえますが、詳細は簡単です。 whenの説明を意図的に避けているようです。依存する補完は、thenApplyのような補完メソッドの呼び出し中にではなく、補完するスレッドで呼び出される場合があります。書かれているように、上の段落は実際にはbeggingギャップを仮定で埋めるためのものです。特に、トピックが並行プログラミングと非同期プログラミングに関係している場合は危険であり、プログラマーが開発した期待の多くが頭に浮かびます。ドキュメントが何を言っていないかを注意深く見てみましょう。

ドキュメントはではありません依存依存補完が登録されていると主張していますbeforecomplete()への呼び出しは補完で実行されます糸。さらに、thenApplyのような完了メソッドを呼び出すと、依存する完了mightが呼び出されると記載されていますが、は呼び出されません完了が登録されるスレッドで完了が呼び出されることを示します(「その他」という単語に注意してください)。

これらは、CompletableFutureを使用してタスクをスケジュールおよび作成するすべての人にとって潜在的に重要なポイントです。次の一連のイベントを考えてみます。

  1. スレッドAは、f.thenApply(c1)を介して依存補完を登録します。
  2. しばらくして、スレッドBがf.complete()を呼び出します。
  3. 同じ頃、スレッドCはf.thenApply(c2)を介して別の依存する完了を登録します。

概念的には、complete()は2つのことを行います。それは、futureの結果をパブリッシュすることと、依存する補完を呼び出そうとすることです。ここで、スレッドCが実行されるとどうなりますかafter結果の値がポストされますが、-beforeスレッドBは_c1_の呼び出しを開始しますか?実装によっては、スレッドCはfが完了したことを確認し、次に_c1_ and _c2_を呼び出すことがあります。または、スレッドCは_c2_を呼び出すためにスレッドBを離れたまま、_c1_を呼び出すことができます。ドキュメントはどちらの可能性も除外していません。これを念頭に置いて、ここではドキュメントでがサポートされていないと仮定しています。

  1. cに登録されている依存補完f補完前は、f.complete()の呼び出し中に呼び出されます。
  2. そのcは、f.complete()が戻るまでに完了まで実行されます。
  3. その依存する補完は、特定の順序(たとえば、登録の順序)で呼び出されます。
  4. 登録されている依存完了beforef完了は、完了完了が登録されているafterfが完了する前に呼び出されます。

別の例を考えてみましょう:

  1. スレッドAがf.complete()を呼び出します。
  2. しばらくして、スレッドBはf.thenApply(c1)を介して完了を登録します。
  3. 同じ頃、スレッドCはf.thenApply(c2)を介して別の完了を登録します。

fが既に完了していることがわかっている場合、f.thenApply(c1)の実行中に_c1_が呼び出され、_c2_が呼び出されると想定したくなるかもしれません。 f.thenApply(c2)の間。さらに、_c1_は、f.thenApply(c1)が戻るまでに実行が完了すると想定することもできます。ただし、ドキュメントはこれらの仮定をサポートしていませんoneを呼び出すスレッドのうち、thenApplyが最終的にboth _c1_および_c2_を呼び出し、他のスレッドはどちらも呼び出しません。

JDKコードを注意深く分析すると、上記の架空のシナリオがどのように実行されるかを判断できます。しかし、それでも(1)移植できない、または(2)変更される可能性のある実装の詳細に依存する可能性があるため、危険です。あなたの最善の策は、javadocsや元のJSR仕様に記載されていないものを想定しないことです。

tldr:想定することに注意し、ドキュメントを作成するときは、できるだけ明確かつ慎重に行ってください。簡潔さは素晴らしいことですが、ギャップを埋める人間の傾向に注意してください。

29
Mike Strobel

Javadoc から:

非非同期メソッドの依存する完了に提供されるアクションは、現在のCompletableFutureを完了するスレッド、または完了メソッドの他の呼び出し元によって実行できます。

より具体的に:

  • fnは、complete()を呼び出したスレッドのコンテキストで、complete()の呼び出し中に実行されます。

  • complete()が呼び出されるまでにthenApply()がすでに終了している場合、thenApply()を呼び出しているスレッドのコンテキストでfnが実行されます。

7
NPE

スレッド化に関しては、APIドキュメントが不足しています。スレッディングとフューチャーがどのように機能するかを理解するには、多少の推論が必要です。 1つの前提から始めます。Asyncの非CompletableFutureメソッドは、それ自体では新しいスレッドを生成しません。作業は既存のスレッドの下で続行されます。

thenApplyは、元のCompletableFutureのスレッドで実行されます。それはcomplete()を呼び出すスレッドか、将来がすでに完了している場合はthenApply()を呼び出すスレッドです。スレッドを制御したい場合(fnが遅い操作である場合は良い考えです)、次にthenApplyAsyncを使用する必要があります。

3
John Kugelman