Java 8, использование.parallel в потоке вызывает ошибку OOM
В книге Java 8 In Action, раздел 7.1.1, авторы утверждают, что поток может выиграть от параллельной обработки, добавив функцию .parallel()
, Они предоставляют простой метод под названием parallelSum(int)
чтобы проиллюстрировать это. Мне было любопытно посмотреть, насколько хорошо это работает, поэтому я выполнил этот код:
package lambdasinaction.chap7;
import java.util.stream.Stream;
public class ParallelPlay {
public static void main(String[] args) {
System.out.println(parallelSum(100_000_000));
}
public static long parallelSum(long n) {
return Stream.iterate(1L, i -> i + 1)
.limit(n)
.parallel()
.reduce(0L, Long::sum);
}
}
К моему удивлению, я получил эту ошибку:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at java.util.concurrent.ForkJoinTask.getThrowableException(Unknown Source)
at java.util.concurrent.ForkJoinTask.reportException(Unknown Source)
at java.util.concurrent.ForkJoinTask.invoke(Unknown Source)
at java.util.stream.SliceOps$1.opEvaluateParallelLazy(Unknown Source)
at java.util.stream.AbstractPipeline.sourceSpliterator(Unknown Source)
at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.util.stream.ReferencePipeline.reduce(Unknown Source)
at lambdasinaction.chap7.ParallelPlay.parallelSum(ParallelPlay.java:15)
at lambdasinaction.chap7.ParallelPlay.main(ParallelPlay.java:8)
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.util.stream.SpinedBuffer.ensureCapacity(Unknown Source)
at java.util.stream.Nodes$SpinedNodeBuilder.begin(Unknown Source)
at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.util.stream.SliceOps$SliceTask.doLeaf(Unknown Source)
at java.util.stream.SliceOps$SliceTask.doLeaf(Unknown Source)
at java.util.stream.AbstractShortCircuitTask.compute(Unknown Source)
at java.util.concurrent.CountedCompleter.exec(Unknown Source)
at java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source)
at java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
at java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
Я использую Java 1.8.0_45 в Windows 7, SP1 с четырехъядерным процессором. В чем дело?
1 ответ
Здесь вы создаете бесконечный поток и затем ограничиваете его. Известны проблемы с параллельной обработкой бесконечных потоков. В частности, нет возможности эффективно разделить задачу на равные части. Внутри используется некоторая эвристика, которая не очень подходит для каждой задачи. В вашем случае гораздо лучше создать конечный поток, используя LongStream.range
:
import java.util.stream.LongStream;
public class ParallelPlay {
public static void main(String[] args) {
System.out.println(parallelSum(100_000_000));
}
public static long parallelSum(long n) {
return LongStream.rangeClosed(1, n).parallel().sum();
}
}
В этом случае движок Stream с самого начала знает, сколько у вас элементов, поэтому он может эффективно разделить задачу. Также обратите внимание, что с помощью LongStream
является более эффективным, так как у вас не будет ненужного бокса.
В общем, избегайте бесконечных потоков, если вы можете решить свою задачу с помощью конечных.