Java 8并行流是否为序列使用相同的线程

时间:2016-04-13 20:56:24

标签: java java-8 java-stream

让我们说我们有这样的事情:

LongStream.range(0, 10).parallel()
.filter(l -> {
  System.out.format("filter: %s [%s]\n", l, Thread.currentThread().getName());
  return l % 2 == 0;
})
.map(l -> {
  System.out.format("map:    %s [%s]\n", l, Thread.currentThread().getName());
  return l;
});

如果您运行此程序,输出将类似于:

filter: 6 [main]
map:    6 [main]
filter: 5 [main]
filter: 4 [ForkJoinPool.commonPool-worker-2]
map:    4 [ForkJoinPool.commonPool-worker-2]
filter: 1 [ForkJoinPool.commonPool-worker-3]
filter: 2 [ForkJoinPool.commonPool-worker-1]
filter: 0 [ForkJoinPool.commonPool-worker-3]
filter: 3 [ForkJoinPool.commonPool-worker-2]
filter: 8 [main]
filter: 7 [ForkJoinPool.commonPool-worker-2]
filter: 9 [ForkJoinPool.commonPool-worker-2]
map:    0 [ForkJoinPool.commonPool-worker-3]
map:    2 [ForkJoinPool.commonPool-worker-1]
map:    8 [main]`

正如我们所看到的,每个long的每个任务序列都由同一个线程执行。这是我们可以依赖的东西,还是巧合?线程可以共享'执行期间的任务?

2 个答案:

答案 0 :(得分:10)

关于副作用的stream package summary部分:

  

如果行为参数确实有副作用,除非明确说明,否则不能保证这些副作用对其他线程的可见性,也不保证对“相同”的不同操作同一个流管道中的元素在同一个线程中执行

答案 1 :(得分:4)

这不是巧合,它是当前在OracleJDK / OpenJDK中实现Stream API的方式:无状态操作(如filtermappeekflatMap)融合一起进入单个操作,在单个线程中顺序执行步骤。然而,引入一些有状态操作可能会改变一些事情。例如,我们添加limit

LongStream.range(0, 10).parallel()
.filter(l -> {
  System.out.format("filter: %s [%s]\n", l, Thread.currentThread().getName());
  return l % 2 == 0;
})
.limit(10)
.map(l -> {
  System.out.format("map:    %s [%s]\n", l, Thread.currentThread().getName());
  return l;
})
.forEach(x -> {});

现在限制引入了一个屏障,将管道分成两部分。结果是这样的:

filter: 8 [ForkJoinPool.commonPool-worker-2]
filter: 9 [ForkJoinPool.commonPool-worker-7]
filter: 0 [ForkJoinPool.commonPool-worker-6]
filter: 1 [ForkJoinPool.commonPool-worker-3]
filter: 4 [ForkJoinPool.commonPool-worker-5]
filter: 2 [ForkJoinPool.commonPool-worker-1]
filter: 6 [main]
filter: 7 [ForkJoinPool.commonPool-worker-4]
filter: 3 [ForkJoinPool.commonPool-worker-6]
filter: 5 [ForkJoinPool.commonPool-worker-2]
map:    0 [ForkJoinPool.commonPool-worker-6]
map:    2 [ForkJoinPool.commonPool-worker-2]
map:    8 [ForkJoinPool.commonPool-worker-4]
map:    6 [main]
map:    4 [ForkJoinPool.commonPool-worker-6]

看到元素#2在FJP-1线程中被过滤,但是在FJP-2线程中被映射。

请注意,正如@Misha正确引用的那样,即使对于无状态操作,也无法保证将使用相同的线程。未来或替代Stream API实现可能会改变此行为(例如,使用生产者 - 消费者方法)。