6.3 并行化流操作

并行化操作流只需改变一个方法调用。如果已经有一个Stream对象,调用它的parallel方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用parallelStream就能立即获得一个拥有并行能力的流。

让我们先来看一个具体的例子,例6-1计算了一组专辑的曲目总长度。它拿到每张专辑的曲目信息,然后得到曲目长度,最后相加得出曲目总长度。

例6-1 串行化计算专辑曲目长度

  1. public int serialArraySum() {
  2. return albums.stream()
  3. .flatMap(Album::getTracks)
  4. .mapToInt(Track::getLength)
  5. .sum();
  6. }

调用parallelStream方法即能并行处理,如例6-2所示,剩余代码都是一样的,并行化就是这么简单!

例6-2 并行化计算专辑曲目长度

  1. public int parallelArraySum() {
  2. return albums.parallelStream()
  3. .flatMap(Album::getTracks)
  4. .mapToInt(Track::getLength)
  5. .sum();
  6. }

读到这里,大家的第一反应可能是立即将手头代码中的stream方法替换为parallelStream方法,因为这样做简直太简单了!先别忙,为了将硬件物尽其用,利用好并行化非常重要,但流类库提供的数据并行化只是其中的一种形式。

我们先要问自己一个问题:并行化运行基于流的代码是否比串行化运行更快?这不是一个简单的问题。回到前面的例子,哪种方式花的时间更多取决于串行或并行化运行时的环境。

以例6-1和例6-2中的代码为准,在一个四核电脑上,如果有10张专辑,串行化代码的速度是并行化代码速度的8倍;如果将专辑数量增至100张,串行化和并行化速度相当;如果将专辑数量增值10 000张,则并行化代码的速度是串行化代码速度的2.5倍。

6.3 并行化流操作 - 图1本章的对比基准只是为了说明问题,如果读者尝试在自己的机器上重现这些实验,得到的结果可能会跟书中的结果大相径庭。

输入流的大小并不是决定并行化是否会带来速度提升的唯一因素,性能还会受到编写代码的方式和核的数量的影响。6.6节会详述和性能有关的细节,但现在还是再来看一个更复杂的例子吧。