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));