为什么具有短curcuit操作的并行Java流计算流的所有元素,而序列流则不计算?
考虑两种测试方法parallel()和sequential(): Threads.sleepSafe()是Thread.sleep()的一个简单包装器,如果传递了0,它将接受异常,并且什么也不做 运行测试方法时,结果如下:为什么具有短curcuit操作的并行Java流计算流的所有元素,而序列流则不计算?,java,parallel-processing,java-8,java-stream,short-circuiting,Java,Parallel Processing,Java 8,Java Stream,Short Circuiting,考虑两种测试方法parallel()和sequential(): Threads.sleepSafe()是Thread.sleep()的一个简单包装器,如果传递了0,它将接受异常,并且什么也不做 运行测试方法时,结果如下: sequential start. working... 0 worked. 0 sequential done. parallel start. working... 1 working... 0 worked. 0 sleeping for 1000 ms ... sl
sequential start.
working... 0
worked. 0
sequential done.
parallel start.
working... 1
working... 0
worked. 0
sleeping for 1000 ms ...
slept for 1000 ms.
worked. 1
parallel done.
sequential()
的操作与我预期的一样,但是parallel()
没有:
我希望parallel()
中的findAny()
在work()
第一次返回时返回(即,对于值0,因为它不睡眠),但它只在值1的work()
完成后返回
为什么?
有没有一种方法可以使
findAny()
在work()
第一次返回时立即返回?如果需要并行流,那么是的,它将同时调用work
方法数次
请注意,如果并行流有1000个元素并使用5个线程,那么并行流最多会调用work
,而不是1000次
如果只想调用一次
work
,则使用顺序流。如果想要并行流,则是,它将同时调用work
方法数次
请注意,如果并行流有1000个元素并使用5个线程,那么并行流最多会调用work
,而不是1000次
如果只想调用
work
一次,则使用顺序流。并行模式下的流API基于一个范例,默认情况下使用maxX线程(其中X等于可用处理器的数量)。如果要增加迭代次数,可以检查此规则
通常,并行流的默认线程池计数可以通过两种方式进行自定义:
- 将并行流执行提交到您自己的ForkJoinPool:
李>yourFJP.submit(()->stream.parallel().forEach(soSomething))
- 使用系统属性更改公共池的大小:
以获得20个线程的目标并行性system.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,“20”)
根据ForkJoin算法的思想,答案基本上是否定的。它“等待”所有线程完成它们的工作。但如前所述,您可以将工作人员的数量限制为单个工作人员。显然,它没有场景,因为这种方法类似于顺序执行,冗余操作会增加额外的开销。并行模式下的流API基于一种范例,默认情况下使用maxX线程(其中X等于可用处理器的数量)。如果要增加迭代次数,可以检查此规则 通常,并行流的默认线程池计数可以通过两种方式进行自定义:
- 将并行流执行提交到您自己的ForkJoinPool:
李>yourFJP.submit(()->stream.parallel().forEach(soSomething))
- 使用系统属性更改公共池的大小:
以获得20个线程的目标并行性system.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,“20”)
根据ForkJoin算法的思想,答案基本上是否定的。它“等待”所有线程完成它们的工作。但如前所述,您可以将工作人员的数量限制为单个工作人员。显然,它不会造成任何场景,因为这种方法类似于顺序执行,冗余操作会增加额外的开销。并行流仍然支持短路,但如果所有线程都将其工作延迟到线程,则使用并行流没有任何好处处理以前的元素时,确认操作尚未结束 因此,只要正确组装最终结果(即丢弃多余的元素),并行流处理的元素数量超出需要,这是预期的行为 这只是你的例子,只包含两个元素,我们只处理了一个元素,超出了需要,可以解释为“所有元素都已处理” 当元素的数量很小和/或实际操作是在流的第一个元素中找到可以预测的东西时,并行处理通常没有什么好处。如果你做这样的事情,事情会变得更有趣
IntStream.range(0, 2000).parallel()
.map(i -> { LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(50)); return i;})
.filter(i->i%397==396)
.findAny();
请注意,终端操作将在返回最终结果之前等待所有工作线程的完成,因此,当在找到结果之前已开始对元素进行求值时,该元素的处理将完成。这是故意的。它确保当应用程序代码在流操作后继续运行时,不会同时访问源集合或lambda表达式访问的其他数据 与之相比: 在几乎所有情况下,终端操作都是急切的,在返回之前完成对数据源的遍历和对管道的处理。只有终端操作
iterator()
和spliterator()
不是
因此,短路并行流不会处理所有元素,但当其他工作线程仍在处理过时的元素时,返回已计算的结果可能需要更长的时间
如果希望提前返回,接受可能仍在运行的后台线程,则流API
IntStream.range(0, 2000).parallel()
.map(i -> { LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(50)); return i;})
.filter(i->i%397==396)
.findAny();
private int work(int i) throws InterruptedException {
System.out.println( "working... " + i );
Thread.sleep(i * 1000);
System.out.println( "worked. " + i );
return i;
}
public void parallel() throws Exception {
System.out.println( "parallel start." );
List<Callable<Integer>> jobs = IntStream.range(0, 100)
.collect(ArrayList::new, (l,i) -> l.add(() -> work(i)), List::addAll);
ExecutorService pool = Executors.newFixedThreadPool(10);
Integer result = pool.invokeAny(jobs);
pool.shutdown();
System.out.println( "parallel done, result="+result );
}