Java 8 Stream 流如何使用3-并行数据处理与性能

本篇内容

  1. 用并行流并行处理数据
  2. 并行流的性能分析
  3. 分支/合并框架
  4. 使用Spliterator分割流
1. 并行流

前面我们简要地提到了Stream接口可以让你非常方便地处理它的元素:可以通过对收集源调用parallelStream方法来把集合转换为并行流。并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。这样一来,你就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让它们都忙起来。

1.1 将顺序流转换为并行流
你可以把流转换成并行流,从而让前面的函数归约过程(也就是求和)
并行运行——对顺序流调用parallel方法
    public static void parallelStream(int n) {
   
        Stream.iterate(1L, i -> i + 1L)
                .limit(n)
                .parallel()
                .reduce(0L, Long::sum);
    }

    请注意,在现实中,对顺序流调用parallel方法并不意味着流本身有任何实际的变化。它在内部实际上就是设了一个boolean标志,表示你想让调用parallel之后进行的所有操作都并行执行。类似地,你只需要对并行流调用sequential方法就可以把它变成顺序流。请注意,你可能以为把这两个方法结合起来,就可以更细化地控制在遍历流时哪些操作要并行执行,哪些要顺序执行。例如,你可以这样做:

但最后一次parallel或sequential调用会影响整个流水线。
在本例中,流水线会并行执行,因为最后调用的是它。
Stream.parallel() 
	  .filter(...) 
	  .sequential() 
	  .map(...) 
	  .parallel() 
	  .reduce();

配置并行流使用的线程池

看看流的parallel方法,你可能会想,并行流用的线程是从哪儿来的?有多少个?怎么自定义这个过程呢?
并行流内部使用了默认的ForkJoinPool(2节会进一步讲到分支/合并框架),它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().availableProcessors()得到的。但是你可以通过系统属性 java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示:
System.setProperty(“java.util.concurrent.ForkJoinPool.common.parallelism”,“12”);
这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值,除非你有很好的理由,否则我们强烈建议你不要修改它。

1.2 测量流性能

    我们声称并行求和方法应该比顺序和迭代方法性能好。然而在软件工程上,靠猜绝对不是什么好办法!特别是在优化性能时,你应该始终遵循三个黄金规则:测量,测量,再测量。

    /**
     * 这个方法接受一个函数和一个long作为参数。它会对传给方法的long应用函数10次,记录
     * 每次执行的时间(以毫秒为单位),并返回最短的一次执行时间。假设你把先前开发的所有方法
     * 都放进了一个名为ParallelStreams的类,你就可以用这个框架来测试顺序加法器函数对前一
     * 千万个自然数求和要用多久
     */
    public static long measureSumPerf(Function<Long, Long> adder, long n) {
   
        long fastest = Long.MAX_VALUE;
        for (int i = 0; i < 10; i++) {
   
            long start = System.nanoTime();
            long sum = adder.apply(n);
            long duration = (System.nanoTime() - start) / 1_000_000;
            System.out.println("Result: " + sum);
            if (duration < fastest) fastest = duration;
        }
        return fastest;
    }

    System.out.println("Sequential sum done in:"
                +measureSumPerf(ParallelStreams::sequentialSum,10_000_000)+" msecs");
       结果:Sequential sum done in:77 msecs
        /**
         * 用传统for循环的迭代版本执行起来应该会快很多,因为它更为底层,更重要的是不需要对
         * 原始类型做任何装箱或拆箱操作。如果你试着测量它的性能,
         */

        System.out.println("Iterative sum done in:"
                +measureSumPerf(ParallelStreams::iterativeSum,10_000_000)+" msecs");
         
         结果:Iterative sum done in:2 msecs
         
        System
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值