Java 8並列ストリームは、たとえばforEach
処理などの消費節でスローされた例外に対してどのように動作しますか?たとえば、次のコード:
final AtomicBoolean throwException = new AtomicBoolean(true);
IntStream.range(0, 1000)
.parallel()
.forEach(i -> {
// Throw only on one of the threads.
if (throwException.compareAndSet(true, false)) {
throw new RuntimeException("One of the tasks threw an exception. Index: " + i);
});
処理された要素をすぐに停止しますか?すでに開始されている要素が完了するのを待ちますか?すべてのストリームが完了するのを待ちますか?例外がスローされた後、ストリーム要素の処理を開始しますか?
いつ戻りますか?例外の直後?要素の全部または一部が消費者によって処理された後ですか?
並列ストリームが例外をスローした後も、要素は処理され続けますか? (これが起こったケースを見つけた)。
ここに原則はありますか?
[〜#〜] edit [〜#〜](15-11-2016)
並列ストリームが早く戻るかどうかを判断しようとすると、それが確定的ではないことがわかりました。
@Test
public void testParallelStreamWithException() {
AtomicInteger overallCount = new AtomicInteger(0);
AtomicInteger afterExceptionCount = new AtomicInteger(0);
AtomicBoolean throwException = new AtomicBoolean(true);
try {
IntStream.range(0, 1000)
.parallel()
.forEach(i -> {
overallCount.incrementAndGet();
afterExceptionCount.incrementAndGet();
try {
System.out.println(i + " Sleeping...");
Thread.sleep(1000);
System.out.println(i + " After Sleeping.");
}
catch (InterruptedException e) {
e.printStackTrace();
}
// Throw only on one of the threads and not on main thread.
if (!Thread.currentThread().getName().equals("main") && throwException.compareAndSet(true, false)) {
System.out.println("Throwing exception - " + i);
throw new RuntimeException("One of the tasks threw an exception. Index: " + i);
}
});
Assert.fail("Should not get here.");
}
catch (Exception e) {
System.out.println("Cought Exception. Resetting the afterExceptionCount to zero - 0.");
afterExceptionCount.set(0);
}
System.out.println("Overall count: " + overallCount.get());
System.out.println("After exception count: " + afterExceptionCount.get());
}
レイトリターンメインスレッドからではない場合。このため、例外がスローされた後、多くのnew要素が処理されていました。私のマシンでは、例外がスローされた後、約200の要素が処理されました。しかし、1000個すべての要素が処理されたわけではありません。ここでのルールは何ですか?例外がスローされたにもかかわらず、さらに多くの要素が処理されたのはなぜですか?
アーリーリターン not(!
)記号。メインスレッドで例外がスローされます。すでに開始された要素のみが処理を終了し、新しい要素は処理されませんでした。ここでのケースは、早く戻ることでした。以前の動作と一致していません。
ここで何が欠けていますか?
いずれかのステージで例外がスローされた場合、他の操作が完了するのを待たずに、例外は呼び出し元に再スローされます。これがForkJoinPoolによる処理方法です。
対照的に、たとえば並列で実行する場合、findFirstは、すべての操作が処理を完了する前に結果を呼び出し元に提示します(すべての操作を完了する前に結果がわかっている場合でも)。
つまり、早く戻りますが、実行中のすべてのタスクは終了します。
最後のコメントに答えるように編集
これはHolgerの回答(コメントのリンク)で非常によく説明されていますが、詳細は次のとおりです。
1)メインスレッドをすべて強制終了すると、これらのスレッドによって処理されるはずだったすべてのタスクも強制終了されます。その数である必要がありますは実際には約250であり、1000のタスクと4つのスレッドがあるため、これは3を返すと思いますか?
int result = ForkJoinPool.getCommonPoolParallelism();
理論的には1000のタスクがあり、4つのスレッドがあり、それぞれが250のタスクを処理することになっています。それらの3つを終了すると、750のタスクが失われます。実行する残りのタスクは250あり、ForkJoinPoolはこれらの250の残っているタスクを実行するために3つの新しいスレッドにまたがります。
あなたが試すことができるいくつかのこと、あなたのストリームをこのように変更してください(ストリームのサイズを変更しないでください):
IntStream.generate(random::nextInt).limit(1000).parallel().forEach
今回は、初期の分割インデックスが不明であり、他の戦略によって選択されているため、さらに多くの操作が終了します。また、これを変更することもできます:
if (!Thread.currentThread().getName().equals("main") && throwException.compareAndSet(true, false)) {
これに:
if (!Thread.currentThread().getName().equals("main")) {
今回は、タスクが小さすぎて分割できないため、ForkJoinPoolによって新しいスレッドが作成されない特定のポイントまで、メイン以外のすべてのスレッドを常に強制終了するため、他のスレッドは必要ありません。この場合、完了するタスクはさらに少なくなります。
2)2番目の例では、コードと同じように、メインスレッドを実際に強制終了すると、他のスレッドの実際の実行が表示されません。それを変更 :
} catch (Exception e) {
System.out.println("Cought Exception. Resetting the afterExceptionCount to zero - 0.");
afterExceptionCount.set(0);
}
// give some time for other threads to finish their work. You could play commenting and de-commenting this line to see a big difference in results.
TimeUnit.SECONDS.sleep(60);
System.out.println("Overall count: " + overallCount.get());
System.out.println("After exception count: " + afterExceptionCount.get());