JDK8并行流

以前写过的一个并行流Demo,记得当初一个群里的小朋友非说并行流执行快,我较真的测试了一次,然而现实说明,没有亲自试验过的说法永远都是不可信的。

虽然我这个逻辑并不能真正的证明实际业务中并行流一定不如基础方法,但是在一定程度上的表达了新技术方案不是随便就可以使用的。

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

/**
 * @author yongqi.wang
 * @version 类说明:计算一到一百万相加
 */
public class test {

    public static void main(String[] args) {
        int i = 1;
        List<Long> lists = new ArrayList<>();
        for (long l = 0L; l < 1000000; l++) {
            lists.add((long) (i += 1L));
        }
        long currentTimeMillis = System.currentTimeMillis();
        Optional<Long> reduce = lists.parallelStream().reduce((a, b) -> a + b);
        long currentTimeMillis2 = System.currentTimeMillis();
        if (reduce.isPresent())
            System.out.println("并行流执行时间:" + (currentTimeMillis2 - currentTimeMillis) + "\t结果:" + reduce.get());
        long sum = 0;
        long currentTimeMillis3 = System.currentTimeMillis();
        for (Long num : lists) {
            sum += num;
        }
        long currentTimeMillis4 = System.currentTimeMillis();
        System.out.println("foreach执行时间:" + (currentTimeMillis4 - currentTimeMillis3) + "\t结果:" + sum);
    }
}

第一次执行:

并行流执行时间:109    结果:500001500000
foreach执行时间:13    结果:500001500000


第二次执行:

并行流执行时间:119    结果:500001500000
foreach执行时间:14    结果:500001500000


第三次执行:

并行流执行时间:109    结果:500001500000
foreach执行时间:10    结果:500001500000
 

但是,一旦你的业务逻辑比较复杂时,那么它就会体现出他的优势,单个业务逻辑200毫秒可以说不是很慢了

public class test {

    public static void main(String[] args) {
        int i = 1;
        List<Long> lists = new ArrayList<>();
        for (long l = 0L; l < 1000; l++) {
            lists.add((long) (i += 1L));
        }
        long currentTimeMillis = System.currentTimeMillis();
        Optional<Long> reduce = lists.parallelStream().reduce((a, b) -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return a + b;
        });
        long currentTimeMillis2 = System.currentTimeMillis();
        if (reduce.isPresent())
            System.out.println("并行流执行时间:" + (currentTimeMillis2 - currentTimeMillis) + "\t结果:" + reduce.get());
        long sum = 0;
        long currentTimeMillis3 = System.currentTimeMillis();
        for (Long num : lists) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sum += num;
        }
        long currentTimeMillis4 = System.currentTimeMillis();
        System.out.println("foreach执行时间:" + (currentTimeMillis4 - currentTimeMillis3) + "\t结果:" + sum);
    }
}

结果:

并行流执行时间:50403    结果:501500
foreach执行时间:200501    结果:501500

下面来看一下它的实现源码

第一个方法,来自Collollection的default方法

/**
 * 这个方法表示以本集合为数据源,返回一个可并行的顺序流
 * 并且这个方法的执行权重大于spliterator()方法,而且它不能返回一个不可变得、同步的或延迟绑定的流
 * 默认实现从集合spliterator()方法获取一个并行流
 * 并行流运行结束返回这个集合中的元素
 */
default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
}

然后StreamSupport.stream方法使用了spliterator()方法的返回值作为方法参数之一,那么首先看看spliterator方法的内容

/**
 * 首先他覆盖了父类Itrable中的default方法实现
 * 调用了Spliterators的方法使用自己为方法参数构建IteratorSpliterator对象
 * 而父类中的方法实现时使用了集合对应的迭代器作为方法参数构建IteratorSpliterator对象。
 */
@Override
default Spliterator<E> spliterator() {
    return Spliterators.spliterator(this, 0);
}

那么下一步就是StreamSupport的stream方法实现了

/**
 * 这个方法用于创建新的顺序或并行流,主要取决于spliterator对象的实现类
 * 到了这里我们就可以分析出,如果使用的是迭代器构建的子类那么应该就是顺序流
 * 如果使用自身,那么构建的就是一个并行流
 * 同时这个方法表示:本spliterator对象只有在流管道的终端操作开始后,才能遍历、拆分或
 * 查询spliterator的估计大小。
 */
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
    Objects.requireNonNull(spliterator);
    return new ReferencePipeline.Head<>(spliterator,
                                        StreamOpFlag.fromCharacteristics(spliterator),
                                        parallel);
}

在之后我们看一下StreamOpFlag的fromCharacteristics查询了这个拆分器的哪个特性用作构建Head的参数。

/**
 * 这个具体实现大家可以自行看看,反正我对于二进制运算比较蒙圈~~
 */
static int fromCharacteristics(Spliterator<?> spliterator) {
    int characteristics = spliterator.characteristics();
    if ((characteristics & Spliterator.SORTED) != 0 && spliterator.getComparator() != null) {
        // Do not propagate the SORTED characteristic if it does not correspond
        // to a natural sort order
        return characteristics & SPLITERATOR_CHARACTERISTICS_MASK & ~Spliterator.SORTED;
    } else {
        return characteristics & SPLITERATOR_CHARACTERISTICS_MASK;
    }
}

//ArrayListSpliterator 的 characteristics方法实现
public int characteristics() {
            return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
        }

最后stream使用了这三个参数构建了这么一个对象,当然构建的是这个抽象类的子类,但是子类没有做其他的修改操作,所以我还是吧父类的构造方法放在这里了。

AbstractPipeline(Spliterator<?> source,
                     int sourceFlags, boolean parallel) {
        this.previousStage = null;
        this.sourceSpliterator = source;
        this.sourceStage = this;
        this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
        // The following is an optimization of:
        // StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
        this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
        this.depth = 0;
        this.parallel = parallel;
    }

最后的reduce()方法就没什么好说的了,它是一个函数式调用的方法,其中使用了断言和数据多维运算模型计算,反正我是只看了一点皮毛,说了也不一定对,就略过了~~

发布了110 篇原创文章 · 获赞 475 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/yongqi_wang/article/details/89464144