并行流Parallel Stream和Spliterator接口

本文详细介绍了Java8中的并行流ParallelStream的使用,以及Spliterator接口的重要性和工作原理。通过实例展示了如何使用并行流进行快速计算,并自定义Spliterator进行元素拆分,同时解释了Spliterator的主要方法,包括tryAdvance、trySplit、estimateSize和characteristics。文章还提供了自定义Spliterator的代码示例,以及如何将其应用于Stream中进行串行和并行操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 Parallel 入门

 public static void main(String[] args) {
   
     long sum = 0L;
     for (long l = 0; l < 10_000_000; l++) {
         sum += l;
     };
 }

最简单传统的写法就是上面这样

我们想要使其基于并行的方式运算将是非常困难的,最起码在JDK 1.7版本以前,我们需要很好地处理数据的分区,需要为每一个分区分配不同的线程,并且对线程进行管理,然后还要处理资源竞争的情况,以及最后等待不同线程任务的结束和汇总最终的结果。

由于Fork Join计算框架的引入,我们可以通过划分不同的RecursiveTask来处理对应的数据分区,但是仍然需要我们根据对应的逻辑,显式地对数据元素进行子任务拆分

参考:ForkJoinPool,这里我已经使用Fork Join实现了一个累加

现在使用并行流Parallel Stream来处理

public  void parallel() {
    LongStream.range(0, 10_000_000)
            .parallel()
            .reduce(0L, Long::sum);
}

2 Spliterator 接口

Spliterator也是Java 8引入的一个新的接口,其主要应用于Stream中,尤其是在并行流进行元素块拆分时主要依赖于Spliterator的方法定义,这与我们在ForkJoinPool中进行子任务拆分是一样的,只不过对Spliterator的引入将任务拆分进行了抽象和提取

2.1 Spliterator接口方法详解

/**
该接口非常类似于迭代器方法,其主要作用是对Stream中的每一个元素进行迭代,
并且交由Consumer进行处理,若返回布尔值true则代表着当前Stream还有元素,若返回false则表明没有元素。
**/
boolean tryAdvance(Consumer<? super T> action)

/**
该接口方法代表着对当前Stream中的元素进行分区,派生出另外的Spliterator以供并行操作,
若返回值为null,则代表着不再派生出新的分区,这一点非常类似于Fork Join中的子任务拆分操作。
**/
trySplit() 

/**
该方法主要用于评估当前Stream中还有多少元素未被处理,
一般进行子任务划分时会将基于该接口方法的返回值作为主要依据。
**/
estimateSize() 

/**
与Collector的特征值接口类似,
该方法主要用于定义当前Spliterator接口的特征值,其包含如下几个值可用于定义。
· SIZED - 能够准确地评估出当前元素的数量。
· SORTED - 数据源是已排序的元素。
· SUBSIZED - 利用trySplit()方法进行子任务拆分后,Spliterator元素可被准确评估。
· CONCURRENT - 数据源可被线程安全地修改。
· DISTINCT - 数据源中的数量是去重的,可以根据equalTo方法进行判断。
· IMMUTABLE - 数据源元素是不会被修改的,比如add、remove等。
· NONNULL - 数据源的每一个元素都非空。
· ORDERED -数据源是有序的元素。
**/
characteristics()

在Java 8中,所有的容器类都增加了对Spliterator的支持,我们可以通过方法直接获取Spliterator。

List<String> list = new ArrayList<>();
// 获取Spliterator
Spliterator<String> spliterator = list.spliterator();
int expected = Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;

// 断言该Spliterator的特征值
assert expected == spliterator.characteristics();

2.2 自定义Spliterator及Stream

通过自定义的方式来加深体会Spliterator的原理

public class MySpliterator<T> implements Spliterator<T> {
    private final T[] elements;
    private int currentIndex = 0;
    private final int CAPACITY;

    public MySpliterator(T[] elements) {
        this.elements = elements;
        CAPACITY = elements.length;
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        // 处理元素
        action.accept(elements[currentIndex++]);
        // 判断Stream中的元素是否已排干
        return currentIndex < CAPACITY;
    }

    /**
     * 拆分
     *
     * @return
     */
    @Override
    public Spliterator<T> trySplit() {
        int remainingSize = CAPACITY - currentIndex;
        // 以10作为基准进行子任务拆分,若当前残留元素数量少于10,则不再拆分
        if (remainingSize < 10) {
            return null;
        }
        // 拆分的过程,进行数组拷贝,并且返回一个新的Spliterator
        int middleSize = (remainingSize) / 2;
        T[] newElements = (T[]) new Object[middleSize];
        System.arraycopy(elements, currentIndex, newElements, 0, middleSize);
        final MySpliterator<T> spliterator = new MySpliterator<>(newElements);
        this.currentIndex = currentIndex + middleSize;
        return spliterator;
    }

    @Override
    public long estimateSize() {
        // 由于数组是确定的,因此可以非常精准地得出Stream中的残留元素
        return CAPACITY - currentIndex;
    }

    @Override
    public int characteristics() {
       // 有序、数量固定、子任务数量也固定,担保不存在非空值,并且不允许改变源
        return Spliterator.ORDERED | Spliterator.SIZED
                | Spliterator.SUBSIZED | Spliterator.NONNULL
                | Spliterator.IMMUTABLE;
    }

}

测试拆分


    public static void main(String[] args) {
        // 定义一个数组,有30个元素。
        Integer[] ints = new Integer[]{1, 2, 3, 4, 5,
                6, 7, 8, 9, 10,
                11, 12, 13, 14, 15,
                16, 17, 18, 19, 20,
                21, 22, 23, 24, 25,
                26, 27, 28, 29, 30
        };
        // 定义我们自定义的Spliterator并且传入数组
        MySpliterator<Integer> mySpliterator = new MySpliterator<>(ints);
        // 调用拆分方法,拆分后s1将被分配1~15之间的元素
        Spliterator s1 = mySpliterator.trySplit();
        // 此刻mySpliterator 的元素为16~30之间的元素
        // 再次调用拆分方法,s2将被分配16~22之间的元素,与此同时,mySpliterator 将保留其余的元素
        Spliterator s2 = mySpliterator.trySplit();
        // 输出s1中的元素
        s1.forEachRemaining(System.out::println);
        System.out.println("==================");
        // 输出s2中的元素
        s2.forEachRemaining(System.out::println);
        System.out.println("==================");

        // 输出mySpliterator 中的元素
        mySpliterator.forEachRemaining(System.out::println);
    }

将会得到如下的输出,输出结果与我们在代码注释中的分析完全一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
==================
16
17
18
19
20
21
22
==================
23
24
25
26
27
28
29
30

Spliterator已经创建完成,想要使其能够应用于Stream之中,还需要基于该Spliterator创建一个全新的Stream,创建方式很简单使用StreamSupport提供的方法即可

  1. 串行
// false代表串行
Stream<Integer> stream = StreamSupport.stream(mySpliterator, false);
// 通过reduce操作对Stream中的元素进行求和
int sum = stream.reduce(0, Integer::sum);
// 断言,与ints之和进行对比,检验自定义的Stream及Spliterator是否存在问题
assert sum == Stream.of(ints).reduce(0, Integer::sum);
// 验证通过
// true代表并行
Stream<Integer> stream = StreamSupport.stream(mySpliterator, true);
Stream<Integer> stream = StreamSupport.stream(mySpliterator, true);
// 通过reduce操作对Stream中的元素进行求和运算
int sum = stream.reduce(0, Integer::sum);
// 断言,与ints之和进行对比,检验我们自定义的Stream及Spliterator是否存在问题
assert sum == Stream.of(ints).reduce(0, Integer::sum);
// 验证通过
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值