考虑两个测试方法parallel()和sequential():
@Test
public void parallel() throws Exception
{
System.out.println( "parallel start." );
IntStream.of( 0, 1 ).parallel().map( this::work ).findAny();
System.out.println( "parallel done." );
}
@Test
public void sequential() throws Exception
{
System.out.println( "sequential start." );
IntStream.of( 0, 1 ).map( this::work ).findAny();
System.out.println( "sequential done." );
}
private int work(int i)
{
System.out.println( "working... " + i );
Threads.sleepSafe( i * 1000 );
System.out.println( "worked. " + i );
return i;
}
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 ...
slept for 1000 ms.
worked. 1
parallel done.
sequential()
按照我的预期运作,但parallel()
没有:
我希望findAny()
中的parallel()
在work()
第一次返回时返回(即对于值0,因为它不会休眠),而是仅在{{1}之后返回1}}也完成了值1。
为什么?
首次work()
返回时,有没有办法让findAny()
返回?
答案 0 :(得分:3)
并行模式下的流API基于ForkJoinPool范例,默认情况下使用max X 线程(其中 X 等于可用数量)处理器)。如果您将增加迭代次数,则可以检查此规则。
通常,并行流的默认线程池计数可以通过两种方式进行自定义:
yourFJP.submit(() -> stream.parallel().forEach(soSomething))
; System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20")
,以获得20个线程的目标并行度。有没有办法让work()在第一次返回时返回findAny()?
根据ForkJoin算法的想法,答案基本上是否。它"等待"而所有的线程都将完成他们的工作。但正如之前所述,您可以将工人数量限制为单个工人。显然它不会产生任何场景,因为这种方法与顺序执行类似,并且冗余操作会带来额外的开销。
答案 1 :(得分:2)
并行流仍然支持短路,但是如果所有线程都推迟了他们的工作,直到处理前一个元素的线程确认操作尚未结束,那么使用并行流没有任何优势。
因此,只要最终结果正确组合,即丢弃多余的元素,并行流就会处理超过必要数量的未指定数量的元素。
这只是你的例子,只包含两个元素,只是处理一个超过必要的元素,可以解释为“所有元素都被处理”。
当元素数量很少和/或实际操作是找到可预测地属于流的第一个元素之一时,并行处理通常没什么好处。如果您执行类似
的操作,事情会变得更有趣IntStream.range(0, 2000).parallel()
.map(i -> { LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(50)); return i;})
.filter(i->i%397==396)
.findAny();
请注意,终端操作将在返回最终结果之前等待所有工作线程的完成,因此当在找到结果时已经开始评估元素时,将完成该元素的处理。这是设计的。它确保在流操作后应用程序代码继续执行时,不会对lambda表达式访问的源集合或其他数据进行并发访问。
在几乎所有情况下,终端操作都是 eager ,在返回之前完成对数据源的遍历和管道的处理。只有终端操作
iterator()
和spliterator()
不是; ...
因此,短路并行流不会处理所有元素,但是当其他工作线程仍在处理过时元素时,可能仍需要更长的时间来返回已经评估的结果。
如果您希望提前返回,接受可能仍在运行的后台线程,则Stream API不适合您。考虑
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 );
}
请注意,这不仅会在第一个作业完成后立即返回,还支持通过中断取消所有已在运行的作业。
答案 2 :(得分:1)
如果你想要一个并行流,那么是的,它会同时多次调用work
方法。
请注意,如果并行流包含1,000个元素并使用5个线程,则并行流最多会调用work
5次,而不是1,000次。
如果您只想拨打work
一次,请使用顺序信息流。