Java reduce操作中并行流的同步问题

Java reduce操作中并行流的同步问题,java,multithreading,collections,java-8,java-stream,Java,Multithreading,Collections,Java 8,Java Stream,我正在尝试使用并行流连接字符串 StringBuffer concat = Arrays.stream(grades) .parallel() .reduce( new StringBuffer(), (sb, s) -> sb.append(s), (sb1, sb2) -> sb1.append(sb2) ); 即使使用收集器(可变re

我正在尝试使用并行流连接字符串

StringBuffer concat = Arrays.stream(grades)
        .parallel()
        .reduce(
                new StringBuffer(),
                (sb, s) -> sb.append(s),
                (sb1, sb2) -> sb1.append(sb2)
        );
即使使用收集器(可变reduce)将是更好的方法。我想知道为什么这不能返回正确的结果

例如,
List grades=List.of(“A”、“B”)

而此管道的非并行版本工作正常。我看到的结果是
BABA
,而它应该是
AB

我已经在使用线程安全的
StringBuffer
,而不是
StringBuilder

我还发现以下代码存在同样的问题

List<Integer> ages = people
            .stream()
            .parallel()
            .reduce(
                Collections.synchronizedList(new ArrayList<>()),
                (list, p) -> { list.add(p.getAge()); return list; },
                (list1, list2) -> { list1.addAll(list2) ; return list1; }
            );
修改:

int sum = numbers.parallelStream().reduce(0, Integer::sum);   Reduction parallellizes well because the implementation can operate on
数据的子集并行,然后合并中间 得到最终正确答案的结果。(即使该语言有 “每一个平行”结构,突变累积法 仍然需要开发人员提供线程安全更新 共享累加变量和,以及所需的同步 可能会消除并行性带来的任何性能增益。) 相反,使用reduce()可以消除并行化 简化操作,并能提供高效的并行库 无需额外同步的实现

从这一点上,我了解到,很有可能进行平行缩减


我是不是遗漏了什么?使用线程安全数据结构还不够吗?

当您执行
新建StringBuffer()
时,您正在创建对单个缓冲区的引用。当您执行
.parallel()
时,两个并行流都被传递到此引用,从而在同一可变缓冲区上运行。空缓冲区首先用“B”减少,然后用“A”减少,然后再减少到自身,从而产生“BABA”

要使用可变结构(如StringBuffers)执行类似操作,请尝试使用
.collect()

StringBuffer concat = Arrays.stream(grades).parallel().collect(
    () -> new StringBuffer(),
    (sb, s) -> sb.append(s),
    (sb1, sb2) -> sb1.append(sb2));

请继续阅读该部分,包括随后的“可变缩减”主题。顺便说一下,您使用同步列表的代码仍然被破坏。好的。因此,对于
收集器
,第一个参数是
列表
供应商
,而不是对
列表
本身的引用(这是
减少
的情况)。因此,每个线程都会创建一个新的容器。我说的对吗?对。从关于第一个参数的文档中可以看出:“supplier-一个创建新结果容器的函数。对于并行执行,此函数可能会被调用多次,每次都必须返回一个新值。”请注意,结果
ABAB
也是可能的,这仍然是
StringBuffer
对此类使用的稳健性的一个标志,然而,例如,向自身添加
列表的行为是完全未指定的。乐趣始于两个以上的元素……顺便说一句,这种(正确的)使用
collect
消除了同步的需要,因此它也可以与
StringBuilder
一起使用。它可以缩短为
.collect(StringBuilder::new,StringBuilder::append,StringBuilder::append)
@Holger,但是
同步列表上的
addAll
线程安全吗?我认为它的行为与
StringBuffer
相同。我尝试使用4个元素的列表,它的行为与
StringBuffer
示例相同。@GeekFactory线程安全在这里是不相关的。说明:“如果在操作进行过程中修改了指定的集合,则此操作的行为未定义。(这意味着如果指定的集合是此列表,且此列表非空,则此调用的行为未定义。)”因此,将
ArrayList
添加到自身具有未定义的行为。它可能在特定环境下的一个实现中表现出特定的行为,但这并不能保证
StringBuffer concat = Arrays.stream(grades).parallel().collect(
    () -> new StringBuffer(),
    (sb, s) -> sb.append(s),
    (sb1, sb2) -> sb1.append(sb2));