Java8系列--并行流与并行流的性能测试

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33429968/article/details/77857349

1 并行流

1.1 声明式编程

  通过前面的学习我们知道,Java8Stream的接口可以实现声明式处理数据,而不必考虑细节处理。
  前面我们一直在接触的是“流”的思想,而且大多是流水线式的单线程处理。
  现在考虑在Java8中,是如何进行多线程操作的。

1.2 并行数据处理

  在Java7之前。并行处理数据基本都是通过开辟多线程来解决的,具体流程如下。

  1. 将数据分成部分
  2. 给每个子部分分配一个子线程
  3. 等待所有的子线程全部结束
  4. 合并子线程
    这样的并行数据处理不稳定、易出错。

1.3 并行流

  在Java8中,Stream接口应用分支/合并框架,将一个数据内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

2 测试并行流的性能

2.1 将顺序流转换为并行流

1.问题情景
  对前n个自然数求和。
2.传统Java顺序流
  代码设计:

public void orderSum(long n) {
        long result = 0;
        for (long i = 1L; i <= n; i++) {
            result += i;
        }
        System.out.println("---顺序流运行结果 sum的值---\r\n" + result);
    }

3.Java8并行流
  代码设计:

public void parallelSum(long n) {
        long result = Stream.iterate(1L, i -> i + 1)
                .limit(n)
                .parallel()
                .reduce(0L, Long::sum);
        System.out.println("---并行流运行结果 sum的值---\r\n" + result);
    }

原理解析
  与传统的求和累加方式不同之处在于,Stream在内部将数据流分成了几块不同的数据块,因此可以单独对不同的数据块并行进行求和归纳操作。最后,将各个子流的求和归纳结果合并起来,就得到整个原始流的归纳结果。
配置并行流使用的线程池
  并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().availableProcessors()得到的。
  也可以通过系统属性java.util.concurrent.ForkJoinPool.common.parallelism来改变线程池大小的,如下所示:

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");

2.2 测量并行流的性能

  下面测试顺序、迭代以及并行流三种方法的性能。
  首先写出性能测试函数,代码设计:
  
1.性能测试函数

public static long meaureParallelSumTest(Function<Long, Long> adder, long n) {
        long fastestTime = Long.MAX_VALUE;//将fastestTime设为long.MAX_VALUE可以保证所有duration都不会比fsatestTime大
        for (int i = 0; i < 10; i++) {
            long startTime = System.nanoTime();//开始时间
            long sum = adder.apply(n);
            long duration = (System.nanoTime() - startTime) / 1000000;
            if (duration < fastestTime) {
                fastestTime = duration;//记录最短的一次
            }
        }
        return fastestTime;
    }
}

  这个方法接受一个函数和一个long作为参数。
  它会对传给方法的long应用函数10次,记录每次执行的时间,单位为毫秒,并返回最短的一次。
2.顺序流
  代码设计:
  

System.out.println("---并行流的最快时间---\r\n" + meaureParallelSumTest(ParallelSumTest::parallelSumTest, 10000000) + "msecs");

  运行一千万次的时间:
  

---顺序流的最快时间---
3msecs

3.并行流
  代码设计:
  

System.out.println("---顺序流的最快时间---\r\n" + meaureParallelSumTest(ParallelSumTest::orderSumTest, 10000000) + "msecs");

  运行一千万次的时间:
  

---并行流的最快时间---
331msecs

4.分析
  由上面的测试结果可以看到,顺序流的运行时间远远快与并行流,似乎有些出乎意料。
  这是因为:
  传统for循环的迭代版本更为底层,更重要的是不需要对原始类型做任何装箱或拆箱操作。
  而并行流的运行是这样的:
  

  • iterate生成的是装箱的对象,必须不断的装箱、拆箱才能进行数字求和;
  • 我们很难把iterate分成多个独立块来并行执行。

3 正确高效使用并行流

猜你喜欢

转载自blog.csdn.net/qq_33429968/article/details/77857349