本篇内容
- 用并行流并行处理数据
- 并行流的性能分析
- 分支/合并框架
- 使用Spliterator分割流
1. 并行流
前面我们简要地提到了Stream接口可以让你非常方便地处理它的元素:可以通过对收集源调用parallelStream方法来把集合转换为并行流。并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。这样一来,你就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让它们都忙起来。
1.1 将顺序流转换为并行流
你可以把流转换成并行流,从而让前面的函数归约过程(也就是求和)
并行运行——对顺序流调用parallel方法
public static void parallelStream(int n) {
Stream.iterate(1L, i -> i + 1L)
.limit(n)
.parallel()
.reduce(0L, Long::sum);
}
请注意,在现实中,对顺序流调用parallel方法并不意味着流本身有任何实际的变化。它在内部实际上就是设了一个boolean标志,表示你想让调用parallel之后进行的所有操作都并行执行。类似地,你只需要对并行流调用sequential方法就可以把它变成顺序流。请注意,你可能以为把这两个方法结合起来,就可以更细化地控制在遍历流时哪些操作要并行执行,哪些要顺序执行。例如,你可以这样做:
但最后一次parallel或sequential调用会影响整个流水线。
在本例中,流水线会并行执行,因为最后调用的是它。
Stream.parallel()
.filter(...)
.sequential()
.map(...)
.parallel()
.reduce();
配置并行流使用的线程池
看看流的parallel方法,你可能会想,并行流用的线程是从哪儿来的?有多少个?怎么自定义这个过程呢?
并行流内部使用了默认的ForkJoinPool(2节会进一步讲到分支/合并框架),它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().availableProcessors()得到的。但是你可以通过系统属性 java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示:
System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,“12”);
这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值,除非你有很好的理由,否则我们强烈建议你不要修改它。
1.2 测量流性能
我们声称并行求和方法应该比顺序和迭代方法性能好。然而在软件工程上,靠猜绝对不是什么好办法!特别是在优化性能时,你应该始终遵循三个黄金规则:测量,测量,再测量。
/**
* 这个方法接受一个函数和一个long作为参数。它会对传给方法的long应用函数10次,记录
* 每次执行的时间(以毫秒为单位),并返回最短的一次执行时间。假设你把先前开发的所有方法
* 都放进了一个名为ParallelStreams的类,你就可以用这个框架来测试顺序加法器函数对前一
* 千万个自然数求和要用多久
*/
public static long measureSumPerf(Function<Long, Long> adder, long n) {
long fastest = Long.MAX_VALUE;
for (int i = 0; i < 10; i++) {
long start = System.nanoTime();
long sum = adder.apply(n);
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Result: " + sum);
if (duration < fastest) fastest = duration;
}
return fastest;
}
System.out.println("Sequential sum done in:"
+measureSumPerf(ParallelStreams::sequentialSum,10_000_000)+" msecs");
结果:Sequential sum done in:77 msecs
/**
* 用传统for循环的迭代版本执行起来应该会快很多,因为它更为底层,更重要的是不需要对
* 原始类型做任何装箱或拆箱操作。如果你试着测量它的性能,
*/
System.out.println("Iterative sum done in:"
+measureSumPerf(ParallelStreams::iterativeSum,10_000_000)+" msecs");
结果:Iterative sum done in:2 msecs