【问题标题】:Non-interference exact meaning in Java 8 streamsJava 8 流中的不干扰确切含义
【发布时间】:2015-09-29 06:49:37
【问题描述】:

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

在关于non-interference的部分,在流包描述中,它说: “对于大多数数据源而言,防止干扰意味着确保在流管道执行过程中完全不修改数据源。”

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

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

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

这个例子取自reliable source(至少可以这么说),所以它应该是正确的。 但是,如果我将stream() 更改为parallelStream(),它仍然安全正确吗? (问题3)

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

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

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

【问题讨论】:

    标签: java lambda java-8 java-stream


    【解决方案1】:

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

    一般来说,你可以自己修改流元素,它不应该被称为“干扰”。请注意,如果流源多次生成相同的可变对象(例如,使用Collections.nCopies(10, new MyMutableObject()).parallelStream()。虽然可以确保多个线程不会同时处理相同的流元素,但如果您的流两次生成相同的元素,例如,在forEach 中修改它时,您肯定会遇到竞争条件。

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

    回到你的问题:

    问题1:不,我们可以改变元素的状态,这不叫干扰(虽然叫statefullness)

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

    问题3:你可以放心使用parallelStream()那里

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

    【讨论】:

    • 换句话说,“干扰”存在于更高的层次。修改流元素的操作可能会在同时修改同一个元素时产生干扰,但在修改不同对象时则不会。同样,ArrayList 本身不是线程安全的,并不排除在并发场景中正确使用ArrayLists 的可能性。一切都是关于,如何使用它......
    • 感谢 Tagir 的清晰而详细的回答。维护集合中的可修改元素是常见的任务,并且使用迭代器完成的集合上的许多操作 - 需要更改元素状态,所以它真的很困扰我。如果我们不能安全地做到这一点(至少在并行流的情况下),它会使流的用处大大降低。
    • Tagir,请注意,有状态 lambda 在流包(以及您提到的链接中)中定义为“其结果取决于在流管道执行期间可能发生变化的任何状态”。
    • 继续,过早进入:)。它没有说明改变状态本身。所以我发布的示例的 lambda (s -> s.setColor(RED)) 有副作用,但根据该定义 - 不是有状态的......
    【解决方案2】:

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

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

    来自您的link

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

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

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

    【讨论】:

    • 感谢您回答蒂姆。具体来说,您在这里带来的引用让我对修改流源元素的状态感到更加不确定,因为它将不修改副作用合法化为“打印出值的调试语句”。它不直接面对我的问题。
    • 我认为如果您正在执行并行操作并且会执行更多操作,请不要修改状态。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-12
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多