为什么说Java8的Stream并行流底层使用了Fork/Join框架

先说结论

Stream 是支持串行和并行执行这两种方式的

如何开启并行?

  • 调用parallel方法即可

Stream.of(1, 2, 3, 4, 5).parallel()

先写一个Java8Stream并行流的demo,如下:

import java.util.stream.Stream;

public class AtomicIntegerArrayTest {
public static void main(String[] args) {
Stream.of(1, 2, 3, 4, 5).parallel().reduce((a, b) -> {
return a + b;
}).ifPresent(System.out::println);
}
}

以上的结果是1-5的累加值,如下:

先看看parallel()方法,进入相关的类AbstractPipeline中,如下:

abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>
extends PipelineHelper<E_OUT> implements BaseStream<E_OUT, S> {
private final AbstractPipeline sourceStage;

private boolean parallel;
@Override
@SuppressWarnings("unchecked")
public final S parallel() {
sourceStage.parallel = true;
return (S) this;
}
}

可以发现parallel()方法做的事很简单,就是标记个并行的标记,设置为true。

那么接下来看看reduce()方法:

abstract class ReferencePipeline<P_IN, P_OUT>
extends AbstractPipeline<P_IN, P_OUT, Stream<P_OUT>>
implements Stream<P_OUT> {
@Override
public final Optional<P_OUT> reduce(BinaryOperator<P_OUT> accumulator) {
return evaluate(ReduceOps.makeRef(accumulator));
}
}

接着evaluate()方法跟下去,如下:

abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>
extends PipelineHelper<E_OUT> implements BaseStream<E_OUT, S> {

final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
assert getOutputShape() == terminalOp.inputShape();
if (linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
linkedOrConsumed = true;

return isParallel()
? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
: terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}

@Override
public final boolean isParallel() {
return sourceStage.parallel;
}
}

从以上的三元表达式中可以得出,调用isParallel()方法会得到之前通过parallel()方法设置的true值,然后就会执行terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))方法,所以接着跟进看看evaluateParallel()方法。

首先会进入TerminalOp接口类,如下:

interface TerminalOp<E_IN, R> {

default <P_IN> R evaluateParallel(PipelineHelper<E_IN> helper,
Spliterator<P_IN> spliterator) {
if (Tripwire.ENABLED)
Tripwire.trip(getClass(), "{0} triggering TerminalOp.evaluateParallel serial default");
return evaluateSequential(helper, spliterator);
}
}

会发现这是接口的默认方法,由于使用的是reduce方法,所以看看其相应的实现类对evaluateParallel()方法的实现,如下:

final class ReduceOps {
@Override
public <P_IN> R evaluateParallel(PipelineHelper helper,
Spliterator<P_IN> spliterator) {
return new ReduceTask<>(this, helper, spliterator).invoke().get();
}
}

这时候会发现,该方法中new了一个ReduceTask类,然后调用了它的invoke()方法,看看ReduceTask类相关信息,最后会发现它的继承链是这样的:

`ReduceTask->AbstractTask->CountedCompleter->ForkJoinTask`
1
可以发现最终跟到了ForkJoinTask类中,然后点击跟进invoke()方法,会发现调用的其实是ForkJoinTask()的invoke()方法,该方法是final修饰的,子类无法重写,如下:

public final V invoke() {
int s;
if ((s = doInvoke() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}

总结

通过以上几步源码的跟踪,可以证明Java8的Stream并行流底层确实是使用了Fork/Join框架。
posted @ 2022-07-27 21:21  方东信  阅读(429)  评论(0编辑  收藏  举报