Java8并行流是否对序列使用相同的线程

Java8并行流是否对序列使用相同的线程,java,java-8,java-stream,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().

假设我们有这样的东西:

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: 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]
如果运行此程序,输出将如下所示:

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的每个任务序列都是由同一个线程执行的。这是我们可以依靠的,还是仅仅是巧合?线程能否在执行期间“共享”任务?

来自“副作用”部分:

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


这不是巧合,而是流API目前在OracleJDK/OpenJDK中的实现方式:无状态操作(如
filter
map
peek
flatMap
)被融合到一个操作中,在单个线程中顺序执行步骤。但是,引入一些有状态操作可能会改变情况。例如,让我们添加一个
限制

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 -> {});
现在,limit引入了一个屏障,将管道分为两部分。结果如下:

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: 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正确引用的,即使对于无状态操作,也不能保证使用相同的线程。未来或替代的流API实现可能会改变这种行为(例如,使用生产者-消费者方法)