Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9, 8, 0, 1)
.stream()
.parallel()
.collect(Collectors.groupingBy(x -> x % 10))
.forEach((x, y) -> System.out.println(x + ":" + y));
/**
* Evaluate the pipeline with a terminal operation to produce a result.
*
* @param the type of result
* @param terminalOp the terminal operation to be applied to the pipeline.
* @return the result
*/
final R evaluate(TerminalOp 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()));
}
在15行,我们可以看到,在求值的时候会检查并行计算的标志位,如果标志了并行计算的话,我们就会并行求值,反之则会串行求值。我们可以进一步进入并行求值的逻辑中,这是一个TerminalOp的默认接口方法,默认实现就是直接调用串行求值,在FindOp、ForEachOp、MatchOp 和 ReduceOp 中得到了覆盖。
@Override
public O evaluateParallel(PipelineHelper helper,
Spliterator spliterator) {
return new FindTask<>(this, helper, spliterator).invoke();
}
如FindOp的代码示例,这四个操作都是创建一个Task的示例,然后执行invoke方法。这些Task的继承关系如图:
@Override
public void compute() {
Spliterator rs = spliterator, ls; // right, left spliterators
long sizeEstimate = rs.estimateSize();
long sizeThreshold = getTargetSize(sizeEstimate);
boolean forkRight = false;
@SuppressWarnings("unchecked") K task = (K) this;
while (sizeEstimate > sizeThreshold && (ls = rs.trySplit()) != null) {
K leftChild, rightChild, taskToFork;
task.leftChild = leftChild = task.makeChild(ls);
task.rightChild = rightChild = task.makeChild(rs);
task.setPendingCount(1);
if (forkRight) {
forkRight = false;
rs = ls;
task = leftChild;
taskToFork = rightChild;
}
else {
forkRight = true;
task = rightChild;
taskToFork = leftChild;
}
taskToFork.fork();
sizeEstimate = rs.estimateSize();
}
task.setLocalResult(task.doLeaf());
task.tryComplete();
}
ReduceTask(ReduceTask parent,
Spliterator spliterator) {
super(parent, spliterator);
this.op = parent.op;
}
@Override
protected ReduceTask makeChild(Spliterator spliterator) {
return new ReduceTask<>(this, spliterator);
}
public final void tryComplete() {
CountedCompleter> a = this, s = a;
for (int c;;) {
if ((c = a.pending) == 0) {
a.onCompletion(s);
if ((a = (s = a).completer) == null) {
s.quietlyComplete();
return;
}
}
else if (U.compareAndSwapInt(a, PENDING, c, c - 1))
return;
}
}
if (parallelism < 0 && // default 1 less than #cores
(parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0)
parallelism = 1;
if (parallelism > MAX_CAP)
parallelism = MAX_CAP;
如何控制parallize 的线程数?
ForkJoinPool pool = new ForkJoinPool(2);
ret = pool.submit(() -> {
return LongStream.range(1, 50 * 1024 * 1024).boxed().collect(Collectors.toList())
.stream()
.parallel()
.map(x -> x * 2)
.filter(x -> x < 1500)
.reduce((x,y) -> x+y)
.get();
}).get();
接下来打算继续深入这两个很有意思的问题:
深入介绍ForkJoin 的底层实现,包括它是如何进行线程调度和cache line sharing 优化的
参考文献: