Java 8流中的“Non-interference”确切含义是什么?

14

非并发数据结构源的流使用需要满足不干扰要求,这是否意味着我们不能在流管道执行期间更改数据结构中的元素状态(除了不能更改源数据结构本身)?(问题1)

在流包描述中关于非干扰的部分中,它说: "对于大多数数据源,防止干扰意味着确保数据源在流管道执行期间没有被修改。"

这段话没有提到修改元素状态吗?

例如,假设“形状”是非线程安全的集合(如ArrayList),下面的代码是否被认为有干扰?(问题2)

shapes.stream() 
      .filter(s -> s.getColor() == BLUE)
      .forEach(s -> s.setColor(RED));

这个例子是从一个可靠来源中提取的(至少如此说),因此应该是正确的。但是,如果我将stream()更改为parallelStream(),它还会安全和正确吗? (问题3)
另一方面,Naftalin Maurice的“掌握Lambdas”,另一个可靠的来源,明确表示通过管道操作更改元素的状态(值)确实是干扰。 从非干扰部分(3.2.3)可以看出:
“但是,流的规则禁止任何线程修改流源,例如更改元素的值,而不仅仅是管道操作。”
如果书中所说的是正确的,那么这是否意味着我们不能使用Stream API来修改元素的状态(使用forEach),而必须使用常规迭代器(或for-each或Iterable.forEach)? (问题4)
2个回答

9
有一类更大的函数叫做“带有副作用的函数”。JavaDoc说明是正确而完整的:这里的干扰指修改可变源。另一种情况是有状态表达式:依赖于应用程序状态或改变该状态的表达式。您可以在Oracle网站上阅读并行性教程。
通常,您可以修改流元素本身,不应称其为“干扰”。但请注意,如果您的流源多次生成相同的可变对象(例如,使用Collections.nCopies(10, new MyMutableObject()).parallelStream()),尽管确保同一流元素不会被几个线程同时处理,但如果您的流产生了两次相同的元素,则在forEach中修改它时可能会出现竞态条件。
因此,尽管状态表达式有时会出现问题,应谨慎使用并避免使用无状态的替代方法,但如果它们不干扰流源,则可能是可以接受的。当需要无状态表达式(例如,在{{link1:Stream.map}}方法中)时,它在API文档中特别提到。在forEach文档中只需要非干扰性。
所以回到你的问题:
问题1:不,我们可以改变元素状态,而这不被称为干扰(虽然被称为有状态性)。
问题2:不,除非您的流源中有重复对象,否则它没有干扰。
问题3:您可以安全地在那里使用parallelStream()
问题4:不,您可以在这种情况下使用Stream API。

3
换句话说,“干扰”存在于更高的层面。当一个操作同时修改同一元素时,它会造成干扰,但如果修改不同的对象,则不会。同样地,一个ArrayList本身不具备线程安全性并不意味着在并发场景下不能正确使用ArrayList。关键是看它是如何被使用的... - Holger
感谢Tagir提供了清晰且详细的答案。在集合中维护可修改元素是一个常见任务,并且许多使用迭代器进行的集合操作需要更改元素状态,因此这确实让我很困扰。如果我们不能安全地执行这些操作(至少在并行流的情况下),那么流将变得不那么有用。 - Shay
Tagir,请注意,有状态的lambda在stream包中被定义为“其结果取决于在流管道执行期间可能发生变化的任何状态”的函数(并且在您提到的链接中也是如此)。 - Shay
继续,早期回车:)。 它并没有明确说明要改变状态。因此,我发布的示例的lambda(s-> s.setColor(RED))具有副作用,但根据该定义-不具有状态... - Shay

1

修改存储在数据结构中对象的状态与重新分配数据结构元素不同。

当其他人写“更改元素的值”时,他们可能是指将新对象分配给现有List索引。

来自您的link

最好避免在传递给流方法的lambda中产生任何副作用。虽然一些副作用(例如打印出值的调试语句)通常是安全的,但从这些lambda访问可变状态可能会导致数据竞争或意外行为,因为lambda可以同时从许多线程执行,并且可能无法按其自然遇到的顺序查看元素。非干扰不仅包括不干扰源,还包括不干扰其他lambda;当一个lambda修改可变状态并且另一个lambda读取它时,就可能出现这种干扰。

只要满足非干扰性要求,我们甚至可以在非线程安全的源(例如ArrayList)上安全地执行并行操作,并获得可预测的结果。

这特别涉及并行性,并且与任何其他并发编程没有区别。修改状态可能会导致线程之间的可见性问题。


谢谢你的回答,Tim。特别是你引用的那句话让我对修改流源元素的状态感到更加不确定,因为它将未修改的副作用合法化为“打印值的调试语句”。它没有直接解决我的问题。 - Shay
1
我认为指导意见是,在进行并行操作并且将要执行更多操作时,不要修改状态。 - Tim Bender

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