CompletableFuture
は、別のスレッドでタスクを実行し(thread-poolを使用)、コールバック関数を提供します。 CompletableFuture
にAPI呼び出しがあるとします。それはAPI呼び出しをブロックしていますか?スレッドは、APIから応答を受け取らないまでブロックされますか? (メインスレッド/ Tomcatスレッドが非ブロッキングになることはわかっていますが、CompletableFutureタスクが実行されているスレッドはどうなりますか?)
私が知る限り、Monoは完全にノンブロッキングです。
これに光を当てて、私が間違っている場合は修正してください。
CompletableFutureに当てはまるのは、それが本当に非同期であり、呼び出し側のスレッドから非同期にタスクを実行できることと、thenXXX
などのAPIを使用して、結果が利用可能になったときに結果を処理できることです。一方、CompletableFuture
は常に非ブロッキングではありません。たとえば、次のコードを実行すると、デフォルトのForkJoinPool
で非同期に実行されます。
_CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
});
_
タスクを実行するThread
のForkJoinPool
が最終的にブロックされることは明らかです。つまり、呼び出しが非ブロックになることを保証することはできません。
一方、CompletableFuture
はAPIを公開しており、真にノンブロッキングにすることができます。
たとえば、次のことをいつでも実行できます。
_public CompletableFuture myNonBlockingHttpCall(Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
return uncompletedFuture;
}
_
ご覧のとおり、CompletableFuture
futureのAPIは、スレッドをブロックせずに必要なときに実行を完了するcomplete
およびcompleteExceptionally
メソッドを提供します。
前のセクションでは、CFの動作の概要を説明しましたが、CompletableFutureとMonoの主な違いは何ですか?
モノをブロックすることもできます。誰も私たちが以下を書くことを妨げません:
_Mono.fromCallable(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
})
_
もちろん、将来にサブスクライブすると、呼び出し側スレッドはブロックされます。しかし、追加のsubscribeOn
演算子を提供することで、常にそれを回避できます。それにもかかわらず、Mono
のより広範なAPIは重要な機能ではありません。
CompletableFuture
とMono
の主な違いを理解するために、前述のmyNonBlockingHttpCall
メソッドの実装に戻りましょう。
_public CompletableFuture myUpperLevelBusinessLogic() {
var future = myNonBlockingHttpCall();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
var errorFuture = new CompletableFuture();
errorFuture.completeExceptionally(new RuntimeException());
return errorFuture;
}
return future;
}
_
CompletableFuture
の場合、メソッドが呼び出されると、別のサービス/リソースへのHTTP呼び出しが熱心に実行されます。いくつかの事前/事後条件を確認した後の実行結果は実際には必要ありませんが、実行が開始され、追加のCPU/DB-Connections/What-Ever-Machine-Resourcesがこの作業に割り当てられます。
対照的に、Mono
型は定義により遅延です。
_public Mono myNonBlockingHttpCallWithMono(Object someData) {
return Mono.create(sink -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
})
});
}
public Mono myUpperLevelBusinessLogic() {
var mono = myNonBlockingHttpCallWithMono();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
return Mono.error(new RuntimeException());
}
return mono;
}
_
この場合、最後のmono
が購読されるまで何も起こりません。したがって、Mono
メソッドによって返されたmyNonBlockingHttpCallWithMono
がサブスクライブされる場合にのみ、Mono.create(Consumer)
に提供されるロジックが実行されます。
さらに先に進むことができます。実行を大幅に遅延させることができます。ご存知かもしれませんが、Mono
はReactive Streams仕様のPublisher
を拡張したものです。 Reactive Streamsの絶叫機能は、バックプレッシャーのサポートです。したがって、Mono
APIを使用して実行できるのは、データが本当に必要であり、サブスクライバーがそれらを使用する準備ができている場合のみです。
_Mono.create(sink -> {
AtomicBoolean once = new AtomicBoolean();
sink.onRequest(__ -> {
if(!once.get() && once.compareAndSet(false, true) {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
});
}
});
});
_
この例では、サブスクライバーが_Subscription#request
_を呼び出したときにのみデータを実行するため、データを受信する準備ができていることを宣言します。
CompletableFuture
は非同期であり、非ブロッキングにすることができますCompletableFuture
は熱心です。実行を延期することはできません。しかし、あなたはそれらをキャンセルすることができます(何もしないよりはましです)Mono
は非同期/非ブロッキングであり、メインのThread
を異なる演算子で構成することにより、異なるMono
で任意の呼び出しを簡単に実行できます。Mono
は非常に遅延しており、サブスクライバーの存在とそのデータの消費準備によって、実行開始を延期することができます。