web-dev-qa-db-ja.com

HashSetからの並列ストリームは並列に実行されません

並行して処理したい要素のコレクションがあります。 Listを使用すると、並列処理が機能します。ただし、Setを使用すると、並行して実行されません。

問題を示すコードサンプルを作成しました。

public static void main(String[] args) {
    ParallelTest test = new ParallelTest();

    List<Integer> list = Arrays.asList(1,2);
    Set<Integer> set = new HashSet<>(list);

    ForkJoinPool forkJoinPool = new ForkJoinPool(4);

    System.out.println("set print");
    try {
        forkJoinPool.submit(() ->
            set.parallelStream().forEach(test::print)
        ).get();
    } catch (Exception e) {
        return;
    }

    System.out.println("\n\nlist print");
    try {
        forkJoinPool.submit(() ->
            list.parallelStream().forEach(test::print)
        ).get();
    } catch (Exception e) {
        return;
    }   
}

private void print(int i){
    System.out.println("start: " + i);
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    }
    System.out.println("end: " + i);
}

これは私がWindows7で得た出力です

set print
start: 1
end: 1
start: 2
end: 2

list print
start: 2
start: 1
end: 1
end: 2

Setの最初の要素は、2番目の要素が処理される前に終了する必要があることがわかります。 Listの場合、2番目の要素は最初の要素が終了する前に開始します。

この問題の原因と、Setコレクションを使用して問題を回避する方法を教えてください。

22
Nemo

並列処理が、指定したフォーク結合プールの並列処理の並列処理と一致しない場合の動作を再現できます。フォーク結合プールの並列処理を10に設定し、コレクション内の要素の数を50に増やした後、リストベースのストリームの並列処理は6にしか上昇しませんが、セットベースのストリームの並列処理は決して上になりません。 2.2。

ただし、タスクをフォーク結合プールに送信してそのプールで並列ストリームを実行するこの手法は、実装の「トリック」であり、動作が保証されていないことに注意してください。実際、並列ストリームの実行に使用されるスレッドまたはスレッドプールはunspecifiedです。デフォルトでは、共通のフォーク結合プールが使用されますが、環境が異なれば、最終的に異なるスレッドプールが使用される可能性があります。 (アプリケーションサーバー内のコンテナーを検討してください。)

Java.util.stream.AbstractTask クラスでは、_LEAF_TARGET_フィールドが実行される分割の量を決定し、それが実行可能な並列処理の量を決定します。このフィールドの値はForkJoinPool.getCommonPoolParallelism()に基づいています。これはもちろん、タスクを実行しているプールではなく、共通プールの並列処理を使用します。

おそらくこれはバグです(OpenJDKの問題 JDK-8190974 を参照)が、この領域全体はとにかく指定されていません。ただし、システムのこの領域は、たとえば、ポリシーの分割、利用可能な並列処理の量、ブロッキングタスクの処理などの問題の観点から、確実に開発が必要です。 JDKの将来のリリースでは、これらの問題のいくつかに対処する可能性があります。

一方、システムプロパティを使用して、共通のフォーク結合プールの並列処理を制御することができます。この行をプログラムに追加すると、

_System.setProperty("Java.util.concurrent.ForkJoinPool.common.parallelism", "10");
_

共通プールでストリームを実行すると(または、十分に高いレベルの並列処理が設定されている独自のプールにストリームを送信する場合)、さらに多くのタスクが並列で実行されることがわかります。

_-D_オプションを使用して、コマンドラインでこのプロパティを設定することもできます。

繰り返しますが、これは保証された動作ではなく、将来変更される可能性があります。ただし、この手法は、近い将来、JDK8の実装で機能する可能性があります。

UPDATE 2019-06-12:バグ JDK-8190974 JDK 10で修正され、修正はにバックポートされました今後のJDK8uリリース(8u222)。

33
Stuart Marks