【Java8实战】使用并行流

除了顺序流外,Java 8中也可以对集合对象调用parallelStream方法或者对顺序流调用parallel方法来生成并行流。并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。这样在使用流处理数据规模较大的集合对象时可以充分的利用多核CPU来提高处理效率。不过在一些情况下,并行流未必会比顺序流快,甚至会慢很多,所以了解如何高效的使用并行流也至关重要。此外,我们也可以调用流的sequential方法,将并行流转换为顺序流。

测试并行流的性能

举个例子,对1到1000的整数求和,观察顺序流和并行流的处理时间:

public class StreamExample {    public static void main(String[] args) {?        StreamExample.test((n) -> LongStream.rangeClosed(1L, n).reduce(0L, Long::sum), 1000L);        StreamExample.test((n) -> LongStream.rangeClosed(1L, n).parallel().reduce(0L, Long::sum), 1000L);    }?    static void test(LongConsumer c, Long n) {        long start = System.currentTimeMillis();        c.accept(n);        long end = System.currentTimeMillis();        System.out.println("处理时间:" + (end - start) + "msc");    }}

运行结果:

处理时间:9msc处理时间:484msc

结果和我们预期的不一致,这是因为在处理数据集规模不大的情况下,将流并行化所带来的额外开销比逻辑代码开销还大。我们将数据集扩大:

StreamExample.test((n) -> LongStream.rangeClosed(1L, n).reduce(0L, Long::sum), 1000000000L);StreamExample.test((n) -> LongStream.rangeClosed(1L, n).parallel().reduce(0L, Long::sum), 1000000000L);

运行结果:

处理时间:2775msc处理时间:725msc

对于较小的数据量,选择并行流不是一个好的决定。并行处理少数几个元素的好处还抵不上并行化造成的额外开销。设N是要处理的元素的总数,Q是一个元素通过流水线的大致处理成本,则N*Q就是这个对成本的一个粗略的定性估计。Q值较高就意味着使用并行流时性能好的可能性比较大。

接着对比下使用并行流处理包装类型的求和与原始类型的求和运行时间对比:

StreamExample.test((n) -> Stream.iterate(1L, a -> a + 1L).limit(n).reduce(0L, Long::sum), 1000000000L);StreamExample.test((n) -> LongStream.rangeClosed(1L, n).parallel().reduce(0L, Long::sum), 1000000000L);

运行结果:

处理时间:21915msc处理时间:920msc

因为iterate生成的是包装类型的对象,必须拆箱成原始类型才能求和,而且我们很难把iterate分成多个独立块来并行执行。所以可以看到来两者间的运行效率差了将近24倍!

在实际中应避免频繁拆装箱;有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元 素顺序的操作,它们在并行流上执行的代价非常大。例如,findAny会比findFirst性 能好,因为它不一定要按顺序来执行。

再看一个例子:

public class StreamExample {    public static void main(String[] args) {?        ArrayList<Long> arrayList = Stream.iterate(1L, a -> a + 1L).limit(10000000L).collect(toCollection(ArrayList::new));        LinkedList<Long> linkedList = Stream.iterate(1L, a -> a + 1L).limit(10000000L).collect(toCollection(LinkedList::new));?        StreamExample.test(() -> arrayList.parallelStream().reduce(0L, Long::sum));        StreamExample.test(() -> linkedList.parallelStream().reduce(0L, Long::sum));    }?    static void test(Runner r) {        long start = System.currentTimeMillis();        r.run();        long end = System.currentTimeMillis();        System.out.println("处理时间:" + (end - start) + "msc");    }}?@FunctionalInterfaceinterface Runner {    void run();}

上面代码对比了使用并行流处理ArrayList和使用并行流处理LinkedList的性能对比,运行结果如下:

处理时间:1258msc处理时间:7933msc

之所以出现这个结果,是因为ArrayList的拆分效率比LinkedList高得多,前者用不着遍历就可以平均拆分,而后者则必须遍历。

使用并行流要考虑流背后的数据结构是否易于分解。用range方法创建的原始类型流也可以快速分解。

下表列出了流的数据源和可分解性:

数据源 可分解性
ArrayList 很好
LinkedList 很差
IntStream.range 很好
Stream.iterate 很差
HashSet
TreeSet

总结

总而言之,使用并行流应该考虑以下几点:

  • 留意拆装箱成本;
  • 流中依赖于元素顺序的操作,在并行流上执行的代价非常大;
  • 考虑流的流水线操作总成本,对于较小的数据量,并不适合使用并行流;
  • 考虑流背后的数据结构是否易于分解,不易分解的数据结构不适合使用并行流。

原文地址:https://www.cnblogs.com/7788IT/p/11625558.html

时间: 2024-10-29 18:41:36

【Java8实战】使用并行流的相关文章

java8新特性——并行流与顺序流

在我们开发过程中,我们都知道想要提高程序效率,我们可以启用多线程去并行处理,而java8中对数据处理也提供了它得并行方法,今天就来简单学习一下java8中得并行流与顺序流. 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流. Java8中将并行流进行了优化,我们可以很容易的对数据进行并行操作.Stream API可以声明性地通过parallel()与scqucntial()在并行流与顺序流之间进行切换. 一.Fork-Join框架 Fork-Join框架:是java7提供

Java8新特性 - 并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流. Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作.Stream API可以声明性地通过parallel()和sequential()在并行流和顺序流之间进行切换. 在了解并行流之前,我们首先需要了解Fork/Join框架 Fork/Join框架 Fork/Join框架:在必要的情况下,将一个大任务进行拆分(fork)成若干个小任务(拆到不可在拆时),在将一个个的小任务运算的结果进行汇总(join). Fo

【Java8实战】使用流收集数据

在上一节中,我们了解到终端操作collect方法用于收集流中的元素,并放到不同类型的结果中,比如List.Set或者Map.其实collect方法可以接受各种Collectors接口的静态方法作为参数来实现更为强大的规约操作,比如查找最大值最小值,汇总,分区和分组等等. 准备工作 为了演示Collectors接口中的静态方法使用,这里创建一个Dish类(菜谱类): public class Dish {   public enum Type {MEAT, FISH, OTHER}?   /**

在使用Java8并行流时的问题分析

最近在使用Java8的并行流时遇到了坑,线上排查问题时花了较多时间,分享出来与大家一起学习与自查 // 此处为坑 List<Java8Demo> copy = Lists.newArrayList(); numbers.parallelStream().forEach(item -> { copy.add(new Java8Demo(item)); }); 上图用到了parallelStrem并行流,在循环内部往共享变量copy内写值,由于ArrayList本身不具备线程安全性,导致得到

读书笔记,《Java8实战》第一章,为什么要关心 Java8

开篇作者就提出,Java8所做的改变在许多方面比java历史上任何一次改变都深远.而且好消息是,这些改变会让你编辑程序来更容易,再也不用写类似类似于以前的swing中的事件处理函数的啰嗦代码了. 关于多线程,作者也提到,从Java的演变路径来看,他一直致力于让并发编程更容易.出错更少.比如在Java1.0里面有线程和锁的概念,这是当时的最佳实践,但事实证明,不具备专门知识的项目团队,很难可靠地使用这些基本模型.然后在Java 5.0的时候就添加了工业级的构建模块,比如线程池和并发集合.然后到Ja

电子书 Java8实战.pdf

本书全面介绍了Java 8 这个里程碑版本的新特性,包括Lambdas.流和函数式编程.有了函数式的编程特性,可以让代码更简洁,同时也能自动化地利用多核硬件.全书分四个部分:基础知识.函数式数据处理.高效Java 8 编程和超YUEJava 8,清晰明了地向读者展现了一幅Java 与时俱进的现代化画卷. 限个人学习使用,不得用于商业用途,请在下载后24小时内删除.备注:资源来自网络,如有不合理可私信我,秒删.电子书 Java8实战.pdf 免费下载https://page55.ctfile.co

Java的演化-Java8实战笔记

一个语言要想一直有活力,它也需要跟随着时代的变化去进步,Java作为一个古老的语言,它其实有太多的历史包袱,在改变的过程中需要考虑很多,但是它也在慢慢的演变,巩固自己的城墙,不让自己被遗忘在历史中(不少的编程语言已经随着时间的推移,消失在人们的视线中).当然,作为一个拿Java语言当成主语言的程序员,它的进步其实也在延长我们的职业生涯. Java8带来了很多新的特性,虽然Java8发布已经很久了,但是一直没有系统的学习,所以这部分一直没有进行总结.前东家考虑到与之前系统的兼容,技术标准将JDK7

Java8 新特性之流式数据处理

一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现. 比如我们希望对一个包含整数的集合中筛选出所有的偶数,并将其封装成为一个新的List返回,那么在java8之前,我们需要通过如下代码实现: List<Integer> evens = new ArrayList<>(); for (final Integer num : nums) { if (num % 2 =

RecursiveTask和RecursiveAction的使用 以及java 8 并行流和顺序流(转)

什么是Fork/Join框架        Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果.比如计算1+2+..+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任