为了让我们的程序运行的更加高效,CPU的使用效率更高,我们可以通过让程序并行执行的方式让所有的CPU都忙碌起来,从而提供程序执行的效率。
有两种方式来实现并行:java8的fork-join框架、java8中的并行流(底层依然是fork-join框架)。
这里我们以计算n以内数字的和为例进行改进,也让我们能够很好的看到效果。
首先,我们定义要求和的最大数为:Long max = 1000000000L;
一、并行流
(一)经典for循环
首先我们使用经典的for循环,串行进行遍历求和:
@Test
public void serial() {
long sum = 0;
long start = System.currentTimeMillis();
for (long i = 0; i <= max; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println(String.format("for循环串行计算,sum:%d,总共耗时为:%d", sum, (end - start)));
}
效果如下:
for循环串行计算,sum:500000000500000000,总共耗时为:363
(二)Stream求和
接下来,我们使用stream来遍历求和,代码如下:
@Test
public void java8Stream() {
long start = System.currentTimeMillis();
Long sum = Stream.iterate(1L, i -> i + 1)
.limit(max)
.reduce(0L, Long::sum);
long end = System.currentTimeMillis();
System.out.println(String.format("java8流式计算,sum:%d,总共耗时为:%d", sum, (end - start)));
}
效果如下:
java8流式计算,sum:500000000500000000,总共耗时为:11660
我们会发现,这种方式比for循环慢了很多,产生的原因主要如下:
- Stream本身也是串行的;
- 在进行计算的时候,我们使用的Stream流会使用包装类,在计算的时候要进行拆箱和装箱过程,会消耗大量的时间。
(三)Stream并行求和
我们可以通过把流转换成并行流来进行计算
@Test
public void java8Parallel() {
long start = System.currentTimeMillis();
long sum = Stream.iterate(1L, i -> i + 1)
.limit(max)
.parallel() //获取并行流
.reduce(0L, Long::sum);
long end = System.currentTimeMillis();
System.out.println(String.format("Java8并行流计算,sum:%d,总共耗时为:%d", sum, (end - start)));
}
效果:
可以看到,直接发生了内存溢出,产生原因如下:
- 和上面一样,包装类会对相率产生极大的影响;
- fork-join框架底层需要使用Spliterator(后续讲解)对迭代器进行切割,进一步出现了问题;
(四)去掉拆装箱的Stream并行
在使用的时候,我们应当尽量避免包装类的转换,所以,我们可以使用LongStream
来获取数据,这样的话,就避免了不必要的拆箱和装箱。其他的场景下,我们也需要注意这一点。
@Test
public void java8ParallelWtihoutPackage() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, max)
.parallel() //获取并行流
.sum();
long end = System.currentTimeMillis();
System.out.println(String.format("Java8并行流计算,去掉装箱拆箱,sum:%d,总共耗时为:%d", sum, (end - start)));
}
效果如下:
Java8并行流计算,去掉装箱拆箱,sum:500000000500000000,总共耗时为:252
并行流的获取
上面演示了串行到并行流的演