web-dev-qa-db-ja.com

長いブロッキング呼び出しを行うためにCompletableFutureでデフォルトの共通フォーク/結合プールを使用することは悪い習慣ですか?

JDBCを使用してバックエンドにクエリを実行するようなブロッキング呼び出しをラップするCompletableFutureがあるとします。この場合、CompletableFuture.supplyAsync()にパラメーターとしてエグゼキューターサービスを渡していないため、バックエンドを介してリソースをフェッチする実際のブロック作業は、共通のFork/Joinプール内のスレッドによって実行する必要があります。 そうではありませんか 悪い習慣 一般的なFJpoolからのスレッドにブロッキング呼び出しを行わせるのですか?ここでの利点は、私のメインが非同期で実行されるブロッキング呼び出しを委任しているため、スレッドはブロッキングしていません。ブロックされているabtJDBC呼び出しを確認してください ここ 。この推論が正しい場合、CompletableFutureでデフォルトの共通FJpoolを使用するオプションがあるのはなぜですか?

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> {
        return unicornService.getUnicorns();
    });

fetchUnicorns.thenAccept(/**Do something with the result*/);
14
Chinmay

(このように)ブロッキング呼び出しを使用しない理由は、非ブロッキングジョブを想定して、既存のCPUコアを利用するように共通プールの並列処理が構成されているためです。ブロックされたスレッドは、同じプールを使用する他のタスクの並列処理を減らします。

しかし、これには公式の解決策があります:

class BlockingGetUnicorns implements ForkJoinPool.ManagedBlocker {
    List<String> unicorns;
    public boolean block() {
        unicorns = unicornService.getUnicorns();
        return true;
    }
    public boolean isReleasable() { return false; }
}
CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> {
        BlockingGetUnicorns getThem = new BlockingGetUnicorns();
        try {
            ForkJoinPool.managedBlock(getThem);
        } catch (InterruptedException ex) {
            throw new AssertionError();
        }
        return getThem.unicorns;
    });

ForkJoinPool.ManagedBlockerは、ワーカースレッドがブロックされようとしていることを認識したときに、Fork/Joinプールが補正スレッドを作成できるようにする潜在的なブロック操作の抽象化です。

はるかに使いやすいことは明らかです

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> unicornService.getUnicorns(),
                                  Executors.newSingleThreadExecutor());

ここに。実稼働環境では、エグゼキュータへの参照を保持し、それを再利用して、最終的にはshutDownを呼び出します。エグゼキュータが再利用されないユースケースの場合、

CompletableFuture<List<String>> fetchUnicorns  = 
    CompletableFuture.supplyAsync(() -> unicornService.getUnicorns(),
                                  r -> new Thread(r).start());

その場合、スレッドはジョブの完了後に自動的に破棄されます。

12
Holger

この推論が正しい場合、CompletableFutureでデフォルトの共通FJpoolを使用するオプションがあるのはなぜですか?

すべての作業がブロックされているわけではないからです。

CompletableFuture.supplyAsync(Supplier<U>, Executor)を使用して、カスタムエグゼキュータでブロック作業をスケジュールするオプションがあります。

1
the8472