JAVA SE 8 的流库-1.14并行流

1.14 并行流

流使得并行处理块操作变得很容易。这个过程几乎是自动的,但是需要遵守一些规则。
首先,必须有一个并行流。可以用Collection.parallelStream()方法从任何集合中获取一个并行流:

Stream<String> parallelWords = words.parallelStream();

而且,parallel方法可以将任意顺序流转换为并行流。

Stream<String> parallelWords =Stream.of(wordArra).parallel();

只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。
当流操作并行运行时,其目标是要让其返回结果与顺序执行时返回的结果相同。重要的是,这些操作可以以任意顺序执行。

假设想要对字符串流中的所有短单词计数:

int[] shortWords = new int[10];
		wordList.parallelStream().forEach(s->
		{
			if(s.length()<10) shortWords[s.length()]++;
		});

这是一段非常非常糟糕的代码。传递给forEach的函数会在多个并发线程中运行,每个都会更新共享的数组。这是一种经典的竞争情况。如果多次运行这个程序,就会发现每次运行都会产生不同的结果,而且每个都是错的。

如果想确保传递给并行流操作的任何函数都可以安全地并行执行,那么需要远离易变状态。
例如:如果用长度将字符串群组,然后分别对他们计数,那么就可以安全地并行化这项计算。

Map<Integer, Long> shortWordCounts = wordList.parallelStream()
				.filter(s->s.length()<10)
				.collect(groupingBy(String::length,counting()));
		System.out.println(shortWordCounts);
  • 警告:传递给并行流操作的函数不应该被堵塞。并行流使用fork-join池来操作流的各个部分。如果多个流操作被堵塞,那么池可能就无法做任何事情了。

默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用Stream.sorted产生的流,都是有序的 。他们的结果是按照原来元素的顺序累积的,因此是完全可预知的。若果运行相拥的操作两次,将会得到完全相同的结果。

排序并不排斥高效的并行处理。例如:当计算stream.map(fun)时,流可以被划分为n的部分,它们会被并行地处理。然后结果将会按照顺序重新组装起来。
当放弃排序需求时,有些操作可以被更有效地并行化。可以通过在流上调用unordered方法。
在有序流中,Stream.distinct方法会保留所有相同元素中的第一个,但这对并行化是一种阻碍,因为处理每个部分的线程 在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法那么所有部分都可以并行的处理(实用共享的集来跟踪重复元素)。

还可以调用通过放弃排序来提高limit方法的速度。如果只想从流中取出任意n个元素,而并不在意到底要获取哪些,那么可以调用:

Stream<String> sample = words.parallelStream().unordered().limit(n);

合并映射表的代价很高昂,正因为这个原因,Collectors.groupByConcurrent方法使用了共享的并发映射表。为了从并行化中获益,映射表中值的顺序不会与流中的顺序相同。

Map<Integer, List<String>> result = wordList.parallelStream()
				.collect(Collectors.groupingByConcurrent(String::length));

当然,如果使用独立于排序的下游收集器,那么就不必在意了。

Map<Integer, Long> wordCounts = wordList.parallelStream()
				.collect(Collectors.groupingByConcurrent(String::length,counting()));

-警告:不要修改执行某项流操作后会将元素返回到流中的集合没搞懂,希望大佬私聊我教教我Thanks♪(・ω・)ノ(即使这种修改是线程安全的)。记住,流不会收集他们的数据,流总是在单独的集合中。如果修改了这样的集合,那么流操作的结果就是未定义的。JDK文档对这项需求并未做出任何约束,并且对顺序流和并行流都采取了这种处理方式。
更准确地讲,因为中间流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍是可行的。
例如,下面的操作尽管并不推荐,但是仍旧可以工作:

	Stream<String> wordss = wordList.stream();
	wordList.add("END");
	long n = wordss.distinct().count();

但是,下面的代码是错误的:

Stream<String> words = wordList.stream();
words.forEach(s->{if(s.length()<12) wordList.remove(s);});

为了让并行流操作正常工作,需要满足大量的条件:

  • 数据应该在内存中。必须等到数据到达时非常低效的。
  • 流应该可以被高效的分成若干个子部分。由数组或平衡二叉树支撑的流都可以工作的很好,但是Stream.iterate返回的结果不行。
  • 流操作的工作量应该具有较大的规模。如果工作负载并不是很大,那么搭建并行计算式所付出的代价就没有什么意义。
  • 流操作不应该被阻塞。

所以说,不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。

package JavaSE8的流库;
import static java.util.stream.Collectors.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ParallelStreams {

	public static void main(String[] args) throws IOException {
		// TODO 自动生成的方法存根
		String contents = new String(Files.readAllBytes(
				Paths.get("alice30.txt")),StandardCharsets.UTF_8);
		List<String> wordList = Arrays.asList(contents.split(" "));

		//错误的做法
		int[] shortWords = new int[10];
		wordList.parallelStream().forEach(s->
		{
			if(s.length()<10) shortWords[s.length()]++;
		});
		System.out.println(Arrays.toString(shortWords));
		
		//再来一次,两次结果可能不同,甚至是错的
		Arrays.fill(shortWords, 0);
		wordList.parallelStream().forEach(s->
		{
			if(s.length()<10) shortWords[s.length()]++;
		});
		System.out.println(Arrays.toString(shortWords));
		
		Map<Integer, Long> shortWordCounts = wordList.parallelStream()
				.filter(s->s.length()<10)
				.collect(groupingBy(String::length,counting()));
		System.out.println(shortWordCounts);
		
		Map<Integer, List<String>> result = wordList.parallelStream()
				.collect(Collectors.groupingByConcurrent(String::length));
		System.out.println(result.get(5));
		
		//两次顺序不同
		result = wordList.parallelStream()
				.collect(Collectors.groupingByConcurrent(String::length));
		System.out.println(result.get(5));
		
		Map<Integer, Long> wordCounts = wordList.parallelStream()
				.collect(Collectors.groupingByConcurrent(String::length,counting()));
		System.out.println(wordCounts);
		
		Stream<String> wordss = wordList.stream();
		wordList.add("END");
		long n = wordss.distinct().count();
		System.out.println(n);
		
		Stream<String> words = wordList.stream();
		words.forEach(s->{if(s.length()<12) wordList.remove(s);});
		
	}

}

运行结果:
在这里插入图片描述
感觉好像有点问题,求大佬赐教!

猜你喜欢

转载自blog.csdn.net/z036548/article/details/84201883