CompletableFutureメソッドについて質問があります。
public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
事はJavaDocがちょうどこれを言うことです:
このステージが正常に完了すると、指定された関数の引数としてこのステージの結果を使用して実行される新しいCompletionStageを返します。例外的な完了に関する規則については、CompletionStageのドキュメントを参照してください。
スレッドについてはどうですか?これはどのスレッドで実行されますか?スレッドプールによって未来が完成したらどうなるでしょうか。
CompletableFuture
docsで指定されているポリシーは、理解を深めるのに役立ちます。
非非同期メソッドの依存完了に提供されるアクションは、現在のCompletableFutureを完了するスレッド、または完了メソッドの他の呼び出し元によって実行できます。
明示的なExecutor引数のないすべての非同期メソッドは、
ForkJoinPool.commonPool()
を使用して実行されます(ただし、少なくとも2つの並列処理レベルをサポートしていない場合は、各スレッドを実行するために新しいスレッドが作成されます))。監視、デバッグ、追跡を簡略化するために、生成されたすべての非同期タスクはマーカーインターフェイスのインスタンスですCompletableFuture.AsynchronousCompletionTask
。
pdate:興味深い分析として、@ Mikeが this answer を読むことについてもアドバイスしますドキュメントの詳細に。
@ nullpointer が指摘しているように、ドキュメントには、知っておくべきことが記載されています。ただし、関連するテキストは驚くほどあいまいであり、ここに投稿されたコメント(および回答)の一部は、ドキュメントでサポートされていない仮定に依存しているようです。したがって、私はそれをバラバラにする価値があると思います。具体的には、この段落を非常に注意深く読む必要があります。
非非同期メソッドの依存する完了に提供されるアクションは、現在のCompletableFutureを完了するスレッド、または完了メソッドの他の呼び出し元によって実行できます。
単純明快に聞こえますが、詳細は簡単です。 whenの説明を意図的に避けているようです。依存する補完は、thenApply
のような補完メソッドの呼び出し中にではなく、補完するスレッドで呼び出される場合があります。書かれているように、上の段落は実際にはbeggingギャップを仮定で埋めるためのものです。特に、トピックが並行プログラミングと非同期プログラミングに関係している場合は危険であり、プログラマーが開発した期待の多くが頭に浮かびます。ドキュメントが何を言っていないかを注意深く見てみましょう。
ドキュメントはではありません依存依存補完が登録されていると主張していますbeforecomplete()
への呼び出しは補完で実行されます糸。さらに、thenApply
のような完了メソッドを呼び出すと、依存する完了mightが呼び出されると記載されていますが、は呼び出されません完了が登録されるスレッドで完了が呼び出されることを示します(「その他」という単語に注意してください)。
これらは、CompletableFuture
を使用してタスクをスケジュールおよび作成するすべての人にとって潜在的に重要なポイントです。次の一連のイベントを考えてみます。
f.thenApply(c1)
を介して依存補完を登録します。f.complete()
を呼び出します。f.thenApply(c2)
を介して別の依存する完了を登録します。概念的には、complete()
は2つのことを行います。それは、futureの結果をパブリッシュすることと、依存する補完を呼び出そうとすることです。ここで、スレッドCが実行されるとどうなりますかafter結果の値がポストされますが、-beforeスレッドBは_c1
_の呼び出しを開始しますか?実装によっては、スレッドCはf
が完了したことを確認し、次に_c1
_ and _c2
_を呼び出すことがあります。または、スレッドCは_c2
_を呼び出すためにスレッドBを離れたまま、_c1
_を呼び出すことができます。ドキュメントはどちらの可能性も除外していません。これを念頭に置いて、ここではドキュメントでがサポートされていないと仮定しています。
c
に登録されている依存補完f
補完前は、f.complete()
の呼び出し中に呼び出されます。c
は、f.complete()
が戻るまでに完了まで実行されます。f
完了は、完了完了が登録されているafterf
が完了する前に呼び出されます。別の例を考えてみましょう:
f.complete()
を呼び出します。f.thenApply(c1)
を介して完了を登録します。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:想定することに注意し、ドキュメントを作成するときは、できるだけ明確かつ慎重に行ってください。簡潔さは素晴らしいことですが、ギャップを埋める人間の傾向に注意してください。
Javadoc から:
非非同期メソッドの依存する完了に提供されるアクションは、現在のCompletableFutureを完了するスレッド、または完了メソッドの他の呼び出し元によって実行できます。
より具体的に:
fn
は、complete()
を呼び出したスレッドのコンテキストで、complete()
の呼び出し中に実行されます。
complete()
が呼び出されるまでにthenApply()
がすでに終了している場合、thenApply()
を呼び出しているスレッドのコンテキストでfn
が実行されます。
スレッド化に関しては、APIドキュメントが不足しています。スレッディングとフューチャーがどのように機能するかを理解するには、多少の推論が必要です。 1つの前提から始めます。Async
の非CompletableFuture
メソッドは、それ自体では新しいスレッドを生成しません。作業は既存のスレッドの下で続行されます。
thenApply
は、元のCompletableFuture
のスレッドで実行されます。それはcomplete()
を呼び出すスレッドか、将来がすでに完了している場合はthenApply()
を呼び出すスレッドです。スレッドを制御したい場合(fn
が遅い操作である場合は良い考えです)、次にthenApplyAsync
を使用する必要があります。