该规范是否保证顺序 Java 流上的操作必须保留在当前线程中?

2022-09-03 16:13:26

规范是否保证顺序 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是否尊重顺序流的相遇顺序?


答案 1

我相信你正在寻找的答案不是那么明确,因为它将取决于消费者和/或分路器及其特性:

在阅读主要引用之前:

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

默认 Stream stream() 返回一个以此集合作为其源的顺序流。当 spliterator() 方法无法返回不可变、并发或后期绑定的拆分器时,应重写此方法。(有关详细信息,请参阅 spliterator()。

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

尽管它们在并行算法中具有明显的实用性,但分离器并不期望是线程安全的。相反,使用拆分器的并行算法的实现应确保拆分器一次仅由一个线程使用。这通常很容易通过串行线程限制来实现,这通常是典型的并行算法的自然结果,这些算法通过递归分解工作。调用 trySplit() 的线程可能会将返回的拆分器移交给另一个线程,而该线程又可以遍历或进一步拆分该拆分器。如果两个或多个线程同时在同一个拆分器上运行,则拆分和遍历的行为未定义。如果原始线程将一个分离器交给另一个线程进行处理,则最好在 tryAdvance() 使用任何元素之前进行该切换,因为某些保证(例如 SIZE 拆分器的 estimateSize() 的准确性)仅在遍历开始之前有效。

分路器和使用者有其一组特征,这将定义保证。让我们假设你正在一个斯特里姆操作。由于拆分器不应该是线程安全的,并且应该处理可能在其他线程中的其他拆分器的元素,无论是否排序,因此保证为空。但是,如果没有拆分,引号将导致以下内容:在一个拆分器下,操作将保留在同一线程中,任何导致拆分的事件都将导致假设为 null,但否则为 true


答案 2

推荐