不干扰 Java 8 流中的确切含义

2022-09-04 22:05:01

使用非并发数据结构源流的非干扰要求是否意味着我们无法在执行流管道期间更改数据结构元素的状态(除此之外,我们无法更改源数据结构本身)?(问题1)

在关于非干扰的部分中,在流包描述中,它说:“对于大多数数据源,防止干扰意味着确保在流管道的执行过程中根本不修改数据源。

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

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

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

此示例取自可靠的来源(至少可以说),因此它应该是正确的。但是,如果我改成“是”,它还能安全、正确吗?(问题3)stream()parallelStream()

另一方面,另一个可靠来源Naftalin Maurice的“掌握Lambdas”清楚地表明,通过管道操作改变元素的状态(值)确实是干扰。从有关不干扰 (3.2.3) 的部分:

“但是流的规则禁止任何线程修改流源 , 例如,包括更改元素的值 , 而不仅仅是管道操作。

如果书中所说的是正确的,这是否意味着我们不能使用Stream API来修改元素的状态(使用),并且必须使用常规迭代器(或for-each,或)来执行此操作?(问题 4)forEachIterable.forEach


答案 1

有一类更大的函数称为“具有副作用的函数”。JavaDoc 语句是正确和完整的:这里的干扰意味着修改可变源。另一种情况是有状态表达式:依赖于应用程序状态或更改此状态的表达式。您可以在 Oracle 站点上阅读并行性教程。

通常,您可以修改流元素本身,它不应称为“干扰”。但是,如果流源多次生成相同的可变对象(例如,使用 .虽然可以确保多个线程不会同时处理同一流元素,但如果流生成同一元素两次,则在 中修改它时肯定会有争用条件, 例如。Collections.nCopies(10, new MyMutableObject()).parallelStream()forEach

因此,虽然有状态表达式有时是有嗅觉的,如果存在无状态的替代方案,则应谨慎使用并避免使用,但如果它们不干扰流源,它们可能是可以的。当需要无状态表达式时(例如,在 Stream.map 方法中),API 文档中会特别提到它。在文档中,只需要不干扰。forEach

所以回到你的问题:

问题1:不,我们可以改变元素状态,它不叫干涉(虽然叫状态丰满)

问题2:不,它没有干扰,除非你的流源中有重复对象)

问题3:您可以安全地在那里使用parallelStream()

问题 4:不,在这种情况下,您可以使用流 API。


答案 2

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

当另一个写“更改元素的值”时,大概他们的意思是好像将新对象分配给现有索引。List

从您的链接

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

只要满足非干扰要求,我们就可以安全地执行并行操作,并且即使在非线程安全源(如ArrayList)上也能获得可预测的结果。

这与并行性特别相关,与任何其他并发编程没有什么不同。修改状态可能会导致线程之间的可见性出现问题。


推荐