《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.14 并行流

1.14 并行流

流使得并行处理块操作变得很容易。这个过程几乎是自动的,但是需要遵守一些规则。首先,必须有一个并行流。可以用Collection.parallelStream()方法从任何集合中获取一个并行流:

只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。
当流操作并行运行时,其目标是要让其返回结果与顺序执行时返回的结果相同。重要的是,这些操作可以以任意顺序执行。
下面的示例是一项你无法完成的任务。假设你想要对字符串流中的所有短单词计数:

这是一种非常非常糟糕的代码。传递给forEach的函数会在多个并发线程中运行,每个都会更新共享的数组。正如我们在卷Ⅰ第14章中所解释的,这是一种经典的竞争情况。如果多次运行这个程序,你很可能就会发现每次运行都会产生不同的计数值,而且每个都是
错的。
你的职责是要确保传递给并行流操作的任何函数都可以安全地并行执行,达到这个目的的最佳方式是远离易变状态。在本例中,如果用长度将字符串群组,然后分别对它们进行计数,那么就可以安全地并行化这项计算。

警告:传递给并行流操作的函数不应该被堵塞。并行流使用fork-join池来操作流的各个部分。如果多个流操作被阻塞,那么池可能就无法做任何事情了。
默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用Stream.sorted产生的流,都是有序的。它们的结果是按照原来元素的顺序累积的,因此是完全可预知的。如果运行相同的操作两次,将会得到完全相同的结果。
排序并不排斥高效的并行处理。例如,当计算stream.map(fun)时,流可以被划分为n的部分,它们会被并行地处理。然后,结果将会按照顺序重新组装起来。
当放弃排序需求时,有些操作可以被更有效地并行化。通过在流上调用unordered方法,就可以明确表示我们对排序不感兴趣。Stream.distinct就是从这种方式中获益的一种操作。在有序的流中,distinct会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法,那么所有部分就可以并行地处理(使用共享的集来跟踪重复元素)。
还可以通过放弃排序要求来提高limit方法的速度。如果只想从流中取出任意n个元素,而并不在意到底要获取哪些,那么可以调用:

正如1.9节所讨论的,合并映射表的代价很高昂。正是因为这个原因,Collectors.groupByConcurrent方法使用了共享的并发映射表。为了从并行化中获益,映射表中值的顺序不会与流中的顺序相同。

警告:不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。记住,流并不会收集它们的数据,数据总是在单独的集合中。如果修改了这样的集合,那么流操作的结果就是未定义的。JDK文档对这项需求并未做出任何约束,并且对顺序流和并行流都采用了这种处理方式。
更准确地讲,因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的。例如,下面的操作尽管并不推荐,但是仍旧可以工作:

为了让并行流正常工作,需要满足大量的条件:

  • 数据应该在内存中。必须等到数据到达是非常低效的。
  • 流应该可以被高效地分成若干个子部分。由数组或平衡二叉树支撑的流都可以工作得很好,但是Stream.iterate返回的结果不行。
  • 流操作的工作量应该具有较大的规模。如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。
  • 流操作不应该被阻塞。
    换句话说,不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。
    程序清单1-8中的示例程序展示了如何操作并行流。
    程序清单1-8 parallel/ParallelStreams.java


时间: 2024-10-02 08:33:42

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.14 并行流的相关文章

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一导读

前 言 致读者 本书是按照Java SE 8完全更新后的<Java核心技术 卷Ⅱ 高级特性(原书第10版)>.卷Ⅰ主要介绍了Java语言的一些关键特性:而本卷主要介绍编程人员进行专业软件开发时需要了解的高级主题.因此,与本书卷Ⅰ和之前的版本一样,我们仍将本书定位于用Java技术进行实际项目开发的编程人员. 编写任何一本书籍都难免会有一些错误或不准确的地方.我们非常乐意听到读者的意见.当然,我们更希望对本书问题的报告只听到一次.为此,我们创建了一个FAQ.bug修正以及应急方案的网站http:/

Java核心技术 卷Ⅰ 基础知识(原书第10版)

Java核心技术系列 Java核心技术 卷Ⅰ 基础知识 (原书第10版) Core Java Volume I-Fundamentals (10th Edition) [美] 凯S.霍斯特曼(Cay S. Horstmann) 著 周立新 陈 波 叶乃文 邝劲筠 杜永萍 译 图书在版编目(CIP)数据 Java核心技术 卷Ⅰ 基础知识(原书第10版) / (美)凯S. 霍斯特曼(Cay S. Horstmann)著:周立新等译. -北京:机械工业出版社,2016.8 (Java核心技术系列) 书

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一第3章 XML

第3章 XML ▲ XML概述 ▲ 使用命名空间 ▲ 解析XML文档 ▲ 流机制解析器 ▲ 验证XML文档 ▲ 生成XML文档 ▲ 使用XPath来定位信息 ▲ XSL转换 Don Box等人在其合著的<Essential XML>(Addison-Wesley出版社2000年出版)的前言中半开玩笑地说道:"可扩展标记语言(Extensible Markup Language,XML)已经取代了Java.设计模式.对象技术,成为软件行业解决世界饥荒的方案."确实,正如你将在

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.10 群组和分区

1.10 群组和分区 在上一节中,你看到了如何收集给定国家的所有语言,但是其处理显得有些冗长.你必须为每个映射表的值都生成单例集,然后指定如何将现有集与新集合并.将具有相同特性的值群聚成组是非常常见的,并且groupingBy方法直接就支持它. 让我们来看看通过国家来群组Locale的问题.首先,构建该映射表: 注意:快速复习一下地点:每个Locale都有一个语言代码(例如英语的en)和一个国家代码(例如美国的US).Locale en_US描述的是美国英语,而en_IE是爱尔兰英语.某些国家有

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.1 从迭代到流的操作

1.1 从迭代到流的操作 在处理集合时,我们通常会迭代遍历它的元素,并在每个元素上执行某项操作.例如,假设我们想要对某本书中的所有长单词进行计数.首先,将所有单词放到一个列表中: 现在,我们可以迭代它了: 在使用流时,相同的操作看起来像下面这样: 流的版本比循环版本要更易于阅读,因为我们不必扫描整个代码去查找过滤和计数操作,方法名就可以直接告诉我们其代码意欲何为.而且,循环需要非常详细地指定操作的顺序,而流却能够以其想要的任何方式来调度这些操作,只要结果是正确的即可. 仅将stream修改为pa

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.12 约简操作

1.12 约简操作 reduce方法是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始持续应用它.如果该函数是求和函数,那么就很容易解释这种机制: 在上面的情况中,reduce方法会计算v0+v1+v2+-,其中vi是流中的元素.如果流为空,那么该方法会返回一个Optional,因为没有任何有效的结果. 注意:在上面的情况中,可以写成reduce(Integer::sum)而不是reduce((x, y) -> x+y). 通常,如果reduce方法有一项

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.9 收集到映射表中

1.9 收集到映射表中 假设我们有一个Stream,并且想要将其元素收集到一个映射表中,这样后续就可以通过它们的ID来查找人员了.Collectors.toMap方法有两个函数引元,它们用来产生映射表的键和值.例如, 如果有多个元素具有相同的键,那么就会存在冲突,收集器将会抛出一个Illegal-StateException对象.可以通过提供第3个函数引元来覆盖这种行为,该函数会针对给定的已有值和新值来解决冲突并确定键对应的值.这个函数应该返回已有值.新值或它们的组合. 在下面的代码中,我们构建

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.8 收集结果

1.8 收集结果 当处理完流之后,通常会想要查看其元素.此时可以调用iterator方法,它会产生可以用来访问元素的旧式风格的迭代器. 或者,可以调用forEach方法,将某个函数应用于每个元素: 在并行流上,forEach方法会以任意顺序遍历各个元素.如果想要按照流中的顺序来处理它们,可以调用forEachOrdered方法.当然,这个方法会丧失并行处理的部分甚至全部 优势. 但是,更常见的情况是,我们想要将结果收集到数据结构中.此时,可以调用toArray,获得由流的元素构成的数组. 因为无

《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一1.3 filter、map和flatMap方法

1.3 filter.map和flatMap方法 流的转换会产生一个新的流,它的元素派生自另一个流中的元素.我们已经看到了f?ilter转换会产生一个流,它的元素与某种条件相匹配.下面,我们将一个字符串流转换为了只包含长单词的另一个流: f?ilter的引元是Predicate,即从T到boolean的函数. 通常,我们想要按照某种方式来转换流中的值,此时,可以使用map方法并传递执行该转换的函数.例如,我们可以像下面这样将所有单词都转换为小写: 这里,我们使用的是带有方法引用的map,但是,通