可以用Collection.parallelStream()
方法从任何集合中获取一个并行流:
Stream<String> parallelWords = words.parallelStream();
parallel
方法可以将任意的顺序流转换为并行流:
Stream<String> parallelWords = Stream.of(wordArray).parallel();
只要在终结方法执行时,流出于并行模式,那么所有中间流操作都将被并行化。
下面示例是无法完成的任务:
words.parallelStream().forEach(
s -> {
if (s.length() < 12)
shortWords[s.length()]++;
});
System.out.println(Arrays.toString(shortWords));
传递给forEach
的函数会在多个并发线程中运行,每个都会更新共享的数组。如果多次运行,可能会发现每次运行都会产生不同的计数值,且每个都是错的。
达到确保传递给并行流操作的任何函数都可以安全地并行执行的目的最佳方式是远离易变状态。
Map<Integer, Long> shortWordCounts =
words.parallelStream()
.filter(s -> s.length() < 10)
.collect(groupingBy(
String::length,
counting()));
传递给并行流操作的函数不应该被阻塞。并行流使用fork-join池来操作流的各个部分。如果多个流操作被阻塞,那么池也可能就无法作任何事情了。
当计算stream.map(fun)
时,流可以被划分为n的部分,会被并行地处理。然后,结果将会按照顺序重新组装起来。
在流上调用unordered
方法,就可以明确标识对排序不感兴趣。
Collectors.groupByConcurrent
方法使用了共享的并发映射表。为了从并行化中获益,映射表中的值的顺序不会与流中的顺序相同。
Map<Integer, List<String>> result = words.parallelStream().collect(
Collectors.groupingByConcurrent(String::length);
// Values aren't collected in stream order
不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。中间的流操作都是惰性的,知道执行终结操作时才对集合进行修改仍旧是可行的
例如,下面的操作尽管不推荐,但仍旧可以龚总:
List<String> wordList = ...;
Stream<String> words = wordList.stream();
wordList.add("END");
long n = words.disinct().count();
但下面的代码是错误的:
Stream<String> words = wordList.stream();
words.forEach(s -> if (s.length() < 12) wordList.remove(s));
// Error-interference
为了让并行流正常工作,需满足大量条件:
- 数据应该在内存中。 必须等到数据到达是非常低效的。
- 流应该可以被高效地分成若干各字部份。 有数组或平衡二叉树支撑的流都可以工作的很好,但是
Stream.iterate
返回的结果不行。 - 流操作的工作量应该具有较大的规模。 如果总工作负载并不是很大,那么搭建并行计算时付出的代价就没有什么意义。
- 流操作不应该被阻塞。
只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。