前言:流使得并行处理块操作变得容易。这个过程几乎是自动的,但需遵守一些规则。
1、获取并行流:
(1)使用Collection.parallelStream()方法从任何集合中获取一个并行流:
List list = Arrays.asList("a", "b", "c", "d", "e");
Stream parallelStream = list.parallelStream();
(2)使用Stream.parallel()方法将任意顺序流转换为并行流:
Stream stream = Stream.of("a", "b", "c", "d", "e");
Stream parallelled = stream.parallel();
2、注意:
(1)只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。
(2)流操作并行运行时,其目标是让其返回结果与顺序执行时返回的结果相同。重要的是,这些操作可以任意顺序执行。
(3)示例:假设要对字符串流中的短单词计数,下面的方法将非常糟糕:
int[] shortWords = new int[12];
Stream stream = Stream.of("abc", "bdfafasf", "c", "ddwadf", "dsadfafe");
stream.parallel().forEach(s -> {
if (s.length() < 12) {
shortWords[s.length()]++;
}
});
System.out.print(Arrays.toString(shortWords));
上例中,传递给forEach的函数会在多个并发线程中运行,每个都会更新共享的数组,这是一种经典的竞争情况。多次运行这个程序将会发现每次的结果都不一样,并且结果都是错的。
为了得到正确的结果,要确保传递给并行流操作的任何函数都可以安全地并行执行,达到这个目的的最佳方式是远离易变状态。例如,用长度将字符串群组,然后分别进行计数:
Stream stream = Stream.of("abc", "bdfafasf", "c", "ddwadf", "dsadfafe");
Map collect = stream.parallel()
.filter(s -> s.length() < 5)
.collect(Collectors.groupingBy(String::length,
Collectors.counting()));
System.out.print(collect);
注意:不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。流并不会收集它们的数据,数据总是在单独的集合中。如果修改了这样的集合,那么流操作的结果就是未定义的。更准确地将,因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的。让并行流正常工作需要满足大量的条件:
(1)传递给并行流操作的函数不应被阻塞。并行流使用fork-join池来操作流的各个部分,如果多个流操作被阻塞,池可能就无法执行任何操作。
(2)数据应该在内存中。必须等到数据到达是非常低效的。
(3)流应该可以被高效地分成若干个字部份。由数组或平衡二叉树支撑的流都可以工作得很好,但Stream.iterate返回的结果不行。
(4)流操作的工作量应该具有较大的规模。如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有意义。
(5)只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。