I used the parallelStream to get the longest string in an array, the code is following, each time it runs, I get different result. AtomicReference
supposes to be thread safe even when used within parallelStream? But why does this happen?
public static void main(String[] args) {
AtomicReference<String> longest = new AtomicReference<>();
LongAccumulator accumulator = new LongAccumulator(Math::max, 0);
List<String> words = Arrays.asList("him", "he", "thanks", "strings", "congratulations", "platform");
words.parallelStream().forEach(
next -> longest.updateAndGet(
current -> {
String result = next.length() > accumulator.intValue() ? next : current;
accumulator.accumulate(next.length());
return result;
}
)
);
System.out.println(longest.get());
}
for one time, I get “congratulations” printed, and some time I get “platform” printed.
答案 0 :(得分:8)
您正在调用LongAccumulator.intValue()
,其记录为:
在缩小基元转换后,将current value作为int返回。
并按照get()
方法进行操作,我们将学习:
返回当前值。返回的值是 NOT 原子快照;在没有并发更新的情况下调用会返回准确的结果,但是在计算值时发生的并发更新可能不会合并。
因此,虽然AtomicReference.updateAndGet
操作是线程安全的,但LongAccumulator.intValue()
和LongAccumulator.accumulate
的并发调用不是。 LongAccumulator
用于执行并发accumulate
操作,然后在所有累积操作完成后获取结果。请注意,即使get()
正在返回正确的快照,intValue()
和后续accumulate()
的调用是两个不同的,因此非原子的操作这一事实使得操作仍然倾向于数据竞赛。
在大多数情况下,如果您发现自己试图在forEach
中操纵数据结构,那么您正在使用错误的工具来完成工作,从而使代码变得不必要地复杂且容易出错。
作为Clayn hinted in a comment,a
words.parallelStream().max(Comparator.comparingInt(String::length))
将简明扼要地完成工作。
答案 1 :(得分:3)
实际上我提到了@Holger已经写过的问题,我有点迟了,但我还是写作@ Holger答案的证明。您可以使用 AtomicReference 的累加器;
以下是示例代码:
public static void main(String[] args)
{
AtomicReference<String> longest = new AtomicReference<>();
List<String> words = Arrays.asList("him", "he", "thanks", "strings", "congratulations", "platform");
words.parallelStream().forEach(next -> {
longest.accumulateAndGet(next, (a,b) ->
a != null && a.length() > b.length() ? a : b
);
});
System.out.println(longest.get());
}