2017-02-27 17 views
9

Javaストリームでいくつかの作業を並列化しようとしています。のは、この単純な例を考えてみましょう:私はforEachを使用する場合Javaストリームジェネレータが順序付けられていないのはなぜですか?

Stream.generate(new Supplier<Integer>() { 
     @Override 
     public Integer get() { 
      return generateNewInteger(); 
     } 
    }) 
    .parallel() 
    .forEachOrdered(new Consumer<Integer>() { 
     @Override 
     public void accept(Integer integer) { 
      System.out.println(integer); 
     } 
    }); 

問題は、それがforEachOrderedためacceptメソッドを呼び出していないということです、それだけで動作します。問題はStream.generateが内部でORDEREDの特性を持たないInfiniteSupplyingSpliteratorを作成しているということです。

質問はなぜですか?どのような順序でデータが生成されるかはわかっているようです。 2番目の質問は、ストリーム要素の生成で並列化されたストリームでforEachOrderedを実行する方法です。

+3

仕様はそう言います。それが完了しない理由は、実装の詳細と組み合わせた*無限*です。 – Holger

+2

これは、ラムダではるかに簡潔に書くことができます。 –

+0

関連していませんが、 '.forEachOrdered(System.out :: println)' –

答えて

10

Stream.generateの順番は、it’s specificationと書いてあります。

実装が可能な限り順番にアイテムを処理しようとしていたのとは違いますが、実際には逆です。オペレーションが順序付けされていないと定義されると、インプリメンテーションは、可能であれば、順序付けられていない性質から利益を得るように試みる。順序付けられていない操作でソースオーダーのように見えるものがある場合、順序付けられていない処理から利益を得る方法がなかったかもしれません。あるいは、実装がすべての機会をまだ使用していないかもしれません。これは将来のバージョンまたは代替実装で変更される可能性があるため、操作が順序付けられていないと指定されている場合は、その順序に依存してはなりません。

と比較すると、順序が明確でない可能性があるので、Stream.generateを未定義として定義しようとする意向が明らかになる可能性があります。 iterateに渡された関数は、前の要素を受け取ります。そのため、要素間には前と後の関係があり、順序付けが行われます。 Stream.generateを渡したサプライヤは、前の要素を受け取らず、言い換えると、機能的署名のみを考慮する場合、前の要素との関係はありません。これはStream.generate(() -> constant)またはStream.generate(Type::new)のようにユースケースのように機能しますが、Stream.generate(instance::statefulOp)では少なくなりますが、これは意図する主な使用例ではないようです。操作がスレッドセーフで、ストリームの順序どおりの性質を持つことができれば、まだ動作します。あなたの例では、進捗状況を作ることはありませんない理由

理由は、forEachOrderedの実装が実際に順序付けられていない自然を考慮していないということですが、つまり、すべてのサブタスクがその要素をバッファリングしてみてください、出会いのために、分割後のチャンクを処理しようとすると、左にあるサブタスクが完了すると、彼らはアクションにそれらを渡すことができます。もちろん、基礎となるInfiniteSupplyingSpliteratorがそれ自身で無限であるサブタスクに分割されるため、バッファリングと無限のソースはよく一緒に再生されません。原則として、その要素をアクションに直接供給することができる最も左のタスクがありますが、そのタスクは待ち行列のどこかにあるように見え、アクティブになるのを待っています。ワーカースレッドが既に他の無限サブ - タスク。最終的には、それが十分長く実行されるようにすると、操作全体がOutOfMemoryErrorで中断されます。

関連する問題