Java 8中的Stream是否可以自动并行而无需显式请求?

22

在我看来,使用Java 8的Stream时,无论是“对象”流还是原始流(即IntStream等),显而易见的代码只需使用:

someStreamableResource.stream().whatever()

但是,相当多的“可流式资源”也有.parallelStream()

阅读javadoc时不清楚.stream()流是否总是顺序的,以及.parallelStream()流是否总是并行的...

然后还有Spliterator,特别是它的.characteristics(),其中之一是它可以是CONCURRENT,甚至是IMMUTABLE

我的直觉是实际上,一个Stream是否可以默认为并行,或者根本不支持并行,是由其基础的Spliterator所决定的...

我是否走在正确的轨道上?我已经阅读了Java文档,反复阅读,但仍无法得出清晰的答案...

5个回答

16
首先,从规范的角度来看。流是并行还是顺序的组成部分。流创建方法应该说明它们创建了顺序流还是并行流(大多数JDK中的方法都是这样),但它们不需要这样做。如果您的流源没有说明,请不要假设。如果有人传递给您一个流,请不要假设。
并行流可以自行退回到顺序流(因为顺序实现是并行实现,只是一个潜在的不完美实现);反之则不然。
现在,从实现的角度来看。在Collections和其他JDK类中的流创建方法中,我们坚持“创建顺序流,除非用户明确要求并行性”的原则。(然而,其他库则会做出不同的选择。如果他们有礼貌,他们会说明他们的行为。)
流并行性与Spliterator之间的关系只能单向前进。Spliterator可以拒绝分割——有效地拒绝任何并行性——但它不能要求客户端将其分割。因此,一个不合作的Spliterator可以破坏并行性,但不能确定它。

“Spliterator可以拒绝分割——有效地拒绝任何并行性”——Spliterator文档在这一点上并不是非常清楚:“使用无法分割的Spliterator的操作……不太可能从并行性中受益。”是否有一个Stream实现可以在没有trySplit的情况下并行化自身?是否有一种更明确的方法来强制流保持顺序? - shmosel
1
@shmosel 调用.sequential()需要流程变为顺序执行。但是并行性只能被请求,而不能被要求,因为有许多因素需要共同作用才能实现真正的并行性。 - Brian Goetz
通常,“流创建者”(我猜你指的是客户端?)不是spliterator的实现者。如果您想要一个顺序流,请在流上调用.sequential()。 - Brian Goetz
不,我的意思是实现者。 - shmosel
@fge 文档没有明确说明这是否足以防止并行化。请参阅我的初始评论。 - shmosel
显示剩余2条评论

2

API 对此问题没有太多说明:

流可以选择顺序或并行执行。(例如,Collection.stream() 创建一个顺序流,Collection.parallelStream() 创建一个并行流。)

关于您的推理,一些中间操作可能不是线程安全的,您可能需要阅读包概述。该包概述深入讨论了中间操作、有状态和无状态,以及如何正确使用 Stream

行为参数中的副作用通常是不鼓励的,因为它们经常会导致无意识的违反无状态要求以及其他线程安全风险。

行为参数是传递给无状态中间操作的参数。

API 不能做出任何假设。

API 可以做出任何它想做的假设。责任在于 API 的用户满足这些假设。但是,假设可能会限制可用性。 Stream API 不鼓励创建无状态中间操作,因为它们不是线程安全的。由于是不鼓励而不是禁止,大多数 Stream 默认情况下都是顺序的。


谢谢您让我对我已经阅读过的内容有了新的认识,显然我之前太迟钝没有发现... - fge

1

好的,自问自答...

认真思考一下之后(这种事情只有我实际提出问题后才会发生),我想到了一个原因...

中间操作可能不是线程安全的;因此,API 不能做出任何假设,因此如果用户想要并行流,它必须明确地请求并确保在流中使用的所有中间操作都是线程安全的。

然而,还有一种有点误导的情况,即Collector;由于Collector无法预先知道它是否会作为终端操作在并行或非并行流上调用,因此合同明确指出“为了安全起见”,任何Collector都必须是线程安全的。


2
实际上,Collector的文档并没有规定Collector必须是线程安全的。确保线程安全是Stream实现使用Collector时的责任。另一方面,“收集器函数必须满足身份和关联性约束”这一点是必需的,以使整个操作是线程安全的... - Holger

1

这里提到了这里:“当你创建一个流时,它总是一个串行流,除非另有规定。”并且这里:“允许该方法(parallelStream)返回一个顺序流。”

CONCURRENTIMMUTABLE与此不直接相关。它们指定底层集合是否可以被修改而不使分裂器无效或者它是否是不可变的。spliterator的特性基本上定义了parallelStream的行为,即trySplit。并行流上的终端操作最终将调用trySplit,无论实现做什么,最终都会定义哪些数据部分(如果有的话)是并行处理的。


1
这个应用目前没有规范限制,但简短的答案是不行。 存在parallelStream()stream()函数,但这只是提供了访问并行或顺序实现常见基本操作以处理流的方式。 目前运行时不能假定您的操作是线程安全的,除非显式使用parallelStream()parallel()调用,否则stream()的默认实现是具有顺序行为。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接