Java8 Stream并行的底层是怎么实现的

153 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

机制

使用并行流,可以有效利用计算机的多CPU硬件,提升逻辑的执行速度。并行流通过将一整个stream划分为多个片段,然后对各个分片流并行执行处理逻辑,最后将各个分片流的执行结果汇总成为一整个流。因为是并发,所以末端操作需要保证线程安全。

image.png

并行是怎么实现的

下面我们还是通过简单的demo来看看是怎么实现的:

package com.study.stream;

import java.util.ArrayList;
import java.util.List;

public class SeeParallelSourceDemo {
    /**
     * 并行筛选并统计信息
     *
     * @param list
     * @return
     */
    private static long parallelFilterAndCount(List<Integer> list) {
        return list.stream().parallel().filter(i -> i % 2 == 0).count();
    }

    public static void main(String[] args) {
        System.out.println(parallelFilterAndCount(new ArrayList<Integer>() {
            {
                add(1);
                add(2);
                add(3);
                add(4);
                add(5);
                add(6);
            }
        }));
    }
}

parallel

public final S parallel() {
    sourceStage.parallel = true;
    return (S) this;
}

可以看到parallel方法只是用来设置sourceStage.parallel为true。

中间操作

中间操作就不看了前文《Java8 Stream的底层是怎么实现的》介绍过,因为stream为懒执行,所以filter应该是一样的。

末端操作

因为部分代码在《Java8 Stream的底层是怎么实现的》已经介绍过,所以我们直奔主题。

parallel.drawio (2).png

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

在前面的parallel方法中设置的

  • AbstractTask#compute
@Override
public void compute() {
    Spliterator<P_IN> rs = spliterator, ls; // right, left spliterators
    long sizeEstimate = rs.estimateSize();
    // 获取阈值大小
    long sizeThreshold = getTargetSize(sizeEstimate);
    boolean forkRight = false;
    @SuppressWarnings("unchecked") K task = (K) this;
    // 元素数量 > 阈值 rs可以进行分割
    while (sizeEstimate > sizeThreshold && (ls = rs.trySplit()) != null) {
        K leftChild, rightChild, taskToFork;
        // 使用ls,rs构造两个新的任务
        task.leftChild  = leftChild = task.makeChild(ls);
        task.rightChild = rightChild = task.makeChild(rs);
        // 设置等待数量为1
        task.setPendingCount(1);
        
        if (forkRight) {
            // 下次让leftChild fork
            forkRight = false;
            rs = ls;
            task = leftChild;
            taskToFork = rightChild;
        }
        else {
            // 下次让rightChild fork
            forkRight = true;
            task = rightChild;
            taskToFork = leftChild;
        }
        // 异步执行,递归
        taskToFork.fork();
        sizeEstimate = rs.estimateSize();
    }
    task.setLocalResult(task.doLeaf());
    task.tryComplete();
}

就是对任务进行拆分,最终合并结果。这里的重点其实在于ForkJoinTask后面再出一篇专门的文章。