Java ArrayList上使用的流API reduce未同步
我正在使用StreamAPI reduce测试字符串数组列表Java ArrayList上使用的流API reduce未同步,java,java-stream,reduce,Java,Java Stream,Reduce,我正在使用StreamAPI reduce测试字符串数组列表 for (int i = 0; i < 100; i++) { Stream<String> s1 = Stream.of("aa", "ab", "c", "ad"); Predicate<String> predicate = t -> t.contains("a"); List<String> strings2 = new
for (int i = 0; i < 100; i++)
{
Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
Predicate<String> predicate = t -> t.contains("a");
List<String> strings2 = new ArrayList<>();
s1.parallel().reduce(new ArrayList<String>(),
new BiFunction<ArrayList<String>, String, ArrayList<String>>()
{
@Override
public ArrayList<String> apply(ArrayList<String> strings, String s)
{
if (predicate.test(s))
{
strings.add(s);
}
return strings;
}
}, new BinaryOperator<ArrayList<String>>()
{
@Override
public ArrayList<String> apply(ArrayList<String> strings,
ArrayList<String> strings2)
{
return strings;
}
}).stream().forEach( //
e -> {
strings2.add(e);
});
if (strings2.contains(null))
{
System.out.println(strings2);
}
}
}
所以我的问题是:
我使用reduce的方式是不正确的,还是应该做一些额外的事情来确保sych?看起来
过滤器
更适合解决这个问题。但是,如果要使用reduce,尤其是在并行使用reduce时,则不能修改累加器对象(本例中的列表)
从:
每次处理元素时,累加器函数也会返回一个新值
当我运行您的代码时,我得到了两份包含null
的列表打印件,后面是ArrayIndexOutOfBoundsException
。可能的原因是两个线程试图同时将元素添加到同一个列表中。异常发生在列表变长之后,但在添加元素之前,因此出现了null
(即空)插槽
ArrayList strings2=
s1.parallel()
.reduce(新的ArrayList(),
(列表,el)->{
如果(el.包含(“a”)){
添加的ArrayList=新的ArrayList(列表);
添加。添加(el);
增加了退货;
}
退货清单;
},
(列表1、列表2)->{
合并的ArrayList=新的ArrayList(列表1);
合并。添加全部(列表2);
返回合并;
});
您不必添加到列表中,而必须复制它,添加到该副本并返回副本。这样,每个线程可以在输入的不同部分上工作,而不会干扰其他部分
此外,您不能在组合器中丢弃部分结果,否则您将得到不完整的结果。您必须合并列表,而不是简单地返回其中一个。
s1.parallel()
创建并行流,如聚合操作和并行流中所述,如果您在操作集合时不修改集合,则可以使用非线程安全集合实现并行
累加器和组合器函数不应修改字符串
列表。使用串行流(s1.reduce(…
)可能是一种解决方案,可以保证“同步”您读过的任何博客,停止阅读它们。List strings2=Stream.of(“aa”、“ab”、“c”、“ad”).parallel().filter(t->t.contains(“a”).collect(Collectors.toList())
是这样做的正确方法。你正在做的事情是不完整的,非常复杂的。@霍尔格,首先,感谢你在这里所做的评论。我的问题不是“最好在这里使用过滤器,或者在这里使用减少,但我确实想学习如何使用减少换句话说,是线程安全的方式。我认为reduce
函数是线程安全的,但是它在这里显示的内容已经证明我是错的,所以我想知道这里是否有线程安全的方式来reduce
。reduce
是线程安全的,如果使用正确的话。事实上,每个线程安全的构造只有在使用时才是安全的d正确。但是当一个博客说“在这种情况下可以使用reduce”时,这是非常错误的。链接教程还提到了创建许多新累加器列表的性能损失。对于这种情况,他们建议使用Stream.collect
,而不是执行“可变”的减少,即,实际上允许您修改累加器对象。在问题的代码中,合并器没有丢弃结果,因为它在两个参数中都收到了相同的列表。但是,这并不是一个正确的解决方案……您提到的“两个线程试图同时将元素添加到同一列表中”这一点,我完全同意这一点,我能解决这一问题的简单原因是使用CopyOnWriteArrayList
而不是ArrayList
。好吧……我想得到的要点是,我想了解更多关于如何正确使用reduce
的知识。对于组合器
,我相信不使用的列表是正确的第二个参数的列表与第一个参数的列表相同。@Robinson要理解reduce
,您必须掌握值的概念。值是不能修改的,一种类型只能有不同的值。您可以计算一个值(或多个值)当您想到不可变列表时,您可以将列表连接到新列表,空列表是连接的标识值,但是当您将指定为第一个参数的列表
修改为reduce
时,它不再是标识值,并且整个操作在语义上是错误的,无论是什么原因d安全。
strings2.contains(null)
ArrayList<String> strings2 =
s1.parallel()
.reduce(new ArrayList<String>(),
(list, el) -> {
if (el.contains("a")) {
ArrayList<String> added = new ArrayList<>(list);
added.add(el);
return added;
}
return list;
},
(list1, list2) -> {
ArrayList<String> merged = new ArrayList<>(list1);
merged.addAll(list2);
return merged;
});