规范是否保证对于顺序Java流的操作必须在当前线程中执行?

15
规范是否保证所有对顺序Java流的操作都在当前线程中执行?(除了“forEach”和“forEachOrdered”)
我明确要求规范,而不是当前实现的情况。我可以自己查看当前实现,不需要麻烦您。但是实现可能会改变,并且还有其他实现。
我之所以问这个问题,是因为ThreadLocals:我使用一个内部使用ThreadLocals的框架。即使像company.getName()这样的简单调用最终也会使用ThreadLocal。我无法更改该框架的设计。至少不能在合理的时间内。
规范在这里似乎令人困惑。 "java.util.stream"包 的文档说明:
如果行为参数具有副作用,则除非明确说明,否则不能保证这些副作用对其他线程可见,也不能保证在同一流管道中的“相同”元素上执行的不同操作在同一线程中执行
即使将流水线限制为生成与流源的遇到顺序一致的结果(例如,IntStream.range(0,5).parallel().map(x -> x*2).toArray()必须生成[0, 2, 4, 6, 8]),也不会做出任何保证,即映射器函数应用于单个元素的顺序或在哪个线程中执行任何行为参数
我理解为: 每个流上的每个操作都可以在不同的线程中执行。但是“forEach”和“forEachOrdered”的文档明确说明:

针对任何给定元素,动作可能会在库选择的任何时间和任何线程中执行。

如果每个流操作可以在未指定的线程中发生,则该语句将是冗余的。因此,相反的情况是否正确:所有串行流上的操作都保证在当前线程中执行,除了“forEach”和“forEachOrdered”?
我已经搜索了关于“Java”、“Stream”和“ThreadLocal”组合的官方答案,但没有找到任何信息。最接近的是Brian Goetz在这里Stack Overflow上回答相关问题的答案,但它是关于顺序而不是线程的,并且仅涉及“forEach”,而不涉及其他流方法:Stream.forEach是否尊重顺序?

forEach文档在开头就指出:“对于并行流管道...”,这仅涉及并行处理;即使有两个句子。 - Eugene
3
关于“对于并行流管道……”:在我所链接的另一个问题中,Brian Goetz指出,“对于并行流管道”的限制仅适用于此一句话。我引用的那句话不受其限制。(如果我理解他的话正确的话。) - user194860
2
确实是个很好的问题 - 请注意,此句子也已添加到Java 9中迭代函数iterate的JavaDocs中,在Java 8中并不存在。 - Hulk
1
相关链接:https://dev59.com/WlcO5IYBdhLWcg3wZQzX(可能是重复的),但我得承认那里唯一的答案并没有真正说服我。 - Hulk
4
@Hulk,我认为只有Stuart Marks或Brian Goetz才能回答这个问题,急切等待中... - Eugene
显示剩余2条评论
1个回答

1
我相信你所寻找的答案不是很明确,它将取决于消费者和/或分离器及其特性:
阅读主要引语之前:

https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#stream

default Stream stream() 返回一个以该集合为源的顺序流。当spliterator()方法无法返回IMMUTABLE、CONCURRENT或late-binding的分割器时,应重写此方法。(有关详细信息,请参见spliterator())

https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html#binding

尽管分割迭代器在并行算法中非常有用,但是它们通常不被认为是线程安全的;相反,使用分割迭代器实现的并行算法应确保每次只有一个线程使用该迭代器。这通常可以通过串行线程限制轻松实现,这种限制通常是递归分解典型并行算法的自然结果。调用trySplit()的线程可以将返回的Spliterator交给另一个线程,后者可以遍历或进一步拆分该Spliterator。如果两个或更多线程同时对同一个分割迭代器进行操作,则拆分和遍历的行为是未定义的。如果原始线程将分割迭代器移交给另一个线程进行处理,则最好在使用tryAdvance()消耗任何元素之前进行移交,因为某些保证(例如对于SIZED分割迭代器的estimateSize()的准确性)仅在开始遍历之前有效。
Spliterators和consumers有它们自己的特点,并且这将定义保证。假设您正在操作流。由于spliterators不应该是线程安全的,而且应该将元素处理给其他可能在其他线程中的spliterators,无论是顺序还是非顺序的,因此它们的保证为null。但是,如果没有发生分裂,则引用将导致以下结果:在一个spliterator下,操作将保持在同一线程中,导致分裂的任何事件都将导致假设为null,但在其他情况下为真。

9
这怎么回答问题了? - Eugene
Spliterators和consumers有它们自己的特点,这将定义保证。假设您正在操作一个流。由于spliterators不应该是线程安全的,并且应该将元素处理给其他可能在其他线程中的spliterators,无论是顺序还是非顺序的,所以它们的保证为null。然而,如果没有发生分裂,则引号将导致以下情况:在一个spliterator下,操作将保持在同一个线程中,任何导致分裂的事件都会导致假设为null,但否则为真。 - Victor
谢谢,我会在工作休息时进一步研究这个问题,以便更好地解释。现在我只是把我的评论复制到那里。 - Victor
是的,我想你是正确的,这就是文档中不再提供保证的地方。由于终端操作不受限于处理分割器,所有处理责任都由第二个线程承担或者从第二个线程中保留数据。我想这会浪费资源,因为程序流程必须等待计算。除非计算涉及到像 futures 这样的东西。所以我的结论是,关于终端操作没有保证。 - Victor
2
即使如此,如果不同线程通过协调访问并建立“先行发生”关系,在同一实例上调用tryAdvance仍然是有效的。只有对同一分割器的并发访问是被禁止的。 - Holger
显示剩余2条评论

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