Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/313.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何使stream reduce成为线程安全的?_Java_Multithreading_Java Stream - Fatal编程技术网

Java 如何使stream reduce成为线程安全的?

Java 如何使stream reduce成为线程安全的?,java,multithreading,java-stream,Java,Multithreading,Java Stream,Java stream API提供了一种通用的.reduce(标识、累加器)方法 从javadocs中可以清楚地看到,累加器应该是一个无状态函数 但是,我有一个关于标识对象的问题,即它应该是线程安全的吗 假设identity是一个java对象,而累加器以一种非原子的方式修改该对象,例如累加器查看标识的状态,然后决定如何准确修改其内部状态。显然,可能会同时运行多个reduce操作。在这种情况下,出现了几个问题: 这个reduce操作是否应该是identity对象范围内的原子操作 仅仅使ident

Java stream API提供了一种通用的
.reduce(标识、累加器)
方法

从javadocs中可以清楚地看到,累加器应该是一个无状态函数

但是,我有一个关于
标识
对象的问题,即它应该是线程安全的吗

假设
identity
是一个java对象,而
累加器以一种非原子的方式修改该对象,例如
累加器
查看
标识的
状态,然后决定如何准确修改其内部状态。显然,可能会同时运行多个reduce操作。在这种情况下,出现了几个问题:

  • 这个reduce操作是否应该是
    identity
    对象范围内的原子操作
  • 仅仅使
    identity
    对象不可变并在每次reduce时返回一个新实例就足够了吗

通常,
acculator
是一个英语单词,意思是:“如果你想要并行,你就完全被套住了”。它就在这个词里:积累——随着时间的推移而积累。除了从头开始,积累直到完成,没有办法把它做好

但是,java通过添加两个需求来解决这个问题:

  • 结合性
    a X(b X c)
    必须产生与
    (a X b)X c
    相同的结果,其中X是累加器函数
  • 身份功能
    ident X a
    必须等于
    a
    ,其中
    ident
    是传递给
    reduce
    的标识,X是累加器函数
  • 让我们以函数
    (a,b)->a+b
    和身份
    0
    为例,如果您的目的是对列表求和,则该函数满足这两个要求

    Java可以通过对任意项求和,然后对这些项的结果求和来实现并行化<代码>[1,5,9,12]
    可以通过先将列表一分为二,然后将这两个子列表分别交给线程进行求和,然后对每个线程提供的答案求和来求和这意味着java将在流中的任意点开始多次累积,并将在任意点多次应用标识作为其累积的一部分,如果您的标识对象本身是可变的,这将带来快速的问题

    基本上没有办法将可变的
    identity
    对象的概念与java的
    reduce
    函数结合起来。从根本上说,它不是为了这样工作而设计的

    与求和示例相反:在
    (a,b)->a+b
    累加器中,不修改a和b,而是修改a和b;相反,它们被组合成新创建的第三个值,这就是您应该如何使用此方法

    与某些其他语言中的
    foldLeft
    不同,这些语言既不要求
    acgregatorfunction(ident,A)
    等于A,也不要求关联性,但根据定义根本无法将其并行化。该foldLeft可用于可变状态。例如,这里有一个伪代码中使用foldLeft求和的例子:(注意,
    newint[1]
    在这里用作可变整数):

    这个概念(累加器函数的LHS始终是相同的东西,即您的标识对象,在您沿着流移动时被修改为集成流中的每个值)与java的reduce不兼容,并且据我所知,java没有(简单的)方法对流执行这种操作

    因此:情况更糟“线程安全”还不够好,它需要是不可变的。一旦它是不可变的,它就是线程安全的

    仅仅使identity对象不可变并在每次reduce时返回一个新实例就足够了吗


    这不仅仅是“足够好”,这或多或少是使用
    reduce

    的唯一明智的方法。文档中包括了这一点,但不是直接暗示的

    标识值必须是累加器函数的标识这意味着对于所有t,累加器.apply(标识,t)等于t

    一旦修改了
    identity
    ,就像您所说的,即使是以线程安全的方式,也违反了上述规则;因此,无法保证预期结果


    对于第二个问题,答案稍微复杂一些。只要没有人滥用身份(通过修改其内部状态),就不必使身份不可变。当然,使其不可变在这方面会有很大帮助。

    考虑将重载与附加的组合器一起使用。如果您希望始终是线程安全的,那么您可以使用多args
    collect()
    或使用
    Collector.Of()
    调用单args方法(甚至您自己的
    Collector
    实现). 这样,您可以拥有多个“标识”,它们最终以线程安全的方式合并在一起;另一种方式是始终返回一个新标识。即,创建一个新实例,复制旧值,修改新实例的状态并返回所述实例。如果
    累加器
    修改
    标识
    ,则
    标识
    累加器
    状态的一部分。因此,
    累加器
    不是无状态的。
    累加器
    不应修改其任何传入参数。这也解决了累加器不知道其参数是否或哪些参数是标识值的问题。在foldLeft上下文中不应使用术语“标识对象”。除此之外,
    collect(()->new int[1],(a,b)->a[0]+=b,(a1,a2)->a1[0]+=a2[0])[0]
    并没有那么难…@rzwitserroot-术语assumulator对我来说是新的,但我认为这是一个有趣的创新哦,这是我灵感更大的打字错误之一:)谢谢你让我知道,我会编辑这个答案。你以前的答案之一可能会
    int sum = stream.foldLeft(new int[1], (int[] a, int b) -> a[0] += b)[0];