web-dev-qa-db-ja.com

モノvsコンプリタブル

CompletableFutureは、別のスレッドでタスクを実行し(thread-poolを使用)、コールバック関数を提供します。 CompletableFutureにAPI呼び出しがあるとします。それはAPI呼び出しをブロックしていますか?スレッドは、APIから応答を受け取らないまでブロックされますか? (メインスレッド/ Tomcatスレッドが非ブロッキングになることはわかっていますが、CompletableFutureタスクが実行されているスレッドはどうなりますか?)

私が知る限り、Monoは完全にノンブロッキングです。

これに光を当てて、私が間違っている場合は修正してください。

15
XYZ

CompletableFutureは非同期です。しかし、それは非ブロッキングですか?

CompletableFutureに当てはまるのは、それが本当に非同期であり、呼び出し側のスレッドから非同期にタスクを実行できることと、thenXXXなどのAPIを使用して、結果が利用可能になったときに結果を処理できることです。一方、CompletableFutureは常に非ブロッキングではありません。たとえば、次のコードを実行すると、デフォルトのForkJoinPoolで非同期に実行されます。

_CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000);
    }
    catch (InterruptedException e) {

    }

    return 1;
});
_

タスクを実行するThreadForkJoinPoolが最終的にブロックされることは明らかです。つまり、呼び出しが非ブロックになることを保証することはできません。

一方、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メソッドを提供します。

モノvsコンプリタブル

前のセクションでは、CFの動作の概要を説明しましたが、CompletableFutureとMonoの主な違いは何ですか?

モノをブロックすることもできます。誰も私たちが以下を書くことを妨げません:

_Mono.fromCallable(() -> {
    try {
        Thread.sleep(1000);
    }
    catch (InterruptedException e) {

    }

    return 1;
})
_

もちろん、将来にサブスクライブすると、呼び出し側スレッドはブロックされます。しかし、追加のsubscribeOn演算子を提供することで、常にそれを回避できます。それにもかかわらず、Monoのより広範なAPIは重要な機能ではありません。

CompletableFutureMonoの主な違いを理解するために、前述の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 AP​​Iを使用して実行できるのは、データが本当に必要であり、サブスクライバーがそれらを使用する準備ができている場合のみです。

_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は非常に遅延しており、サブスクライバーの存在とそのデータの消費準備によって、実行開始を延期することができます。
21
Oleh Dokuka