为什么不建议使用基于AtomicInteger的Stream解决方案?

Kar*_*tik 6 java thread-safety java-8 java-stream atomicinteger

说我有这个水果清单: -

List<String> f = Arrays.asList("Banana", "Apple", "Grape", "Orange", "Kiwi");
Run Code Online (Sandbox Code Playgroud)

我需要在每个水果前面加一个序列号并打印出来.水果或序列号的顺序无关紧要.所以这是一个有效的输出: -

4. Kiwi
3. Orange
1. Grape
2. Apple
5. Banana
Run Code Online (Sandbox Code Playgroud)

解决方案#1

AtomicInteger number = new AtomicInteger(0);

String result = f.parallelStream()
        .map(i -> String.format("%d. %s", number.incrementAndGet(), i))
        .collect(Collectors.joining("\n"));
Run Code Online (Sandbox Code Playgroud)

解决方案#2

String result = IntStream.rangeClosed(1, f.size())
        .parallel()
        .mapToObj(i -> String.format("%d. %s", i, f.get(i - 1)))
        .collect(Collectors.joining("\n"));
Run Code Online (Sandbox Code Playgroud)

题

为什么解决方案#1是一种不好的做法?我在很多地方已经看到AtomicInteger基础解决方案很糟糕(比如在这个答案中),特别是在并行流处理中(这就是我使用上面的并行流来尝试遇到问题的原因).

我查看了这些问题/答案: -
在哪些情况下,Stream操作应该是有状态的?
使用AtomicInteger在Stream中进行索引是一种合法的方式吗?
Java 8:计算lambda迭代的首选方法?

他们只是提到(除非我错过了什么)"可能会出现意想不到的结果".像什么?它可以在这个例子中发生吗?如果没有,你能为我提供一个可能发生的例子吗?

至于" 不保证应用映射器函数的顺序 ",那么这就是并行处理的本质,所以我接受它,而且,在这个特定的例子中,顺序无关紧要.

AtomicInteger 是线程安全的,所以它不应该是并行处理的问题.

有人可以提供使用这种基于状态的解决方案时会出现问题的示例吗?

And*_*lko 3

另请注意,尝试从行为参数访问可变状态会给您带来安全性和性能方面的错误选择;如果您不同步对该状态的访问,则会出现数据争用,因此您的代码会被破坏,但如果您同步对该状态的访问,则可能会出现争用破坏您寻求从中受益的并行性的风险。最好的方法是完全避免有状态的行为参数来流操作;通常有一种方法可以重组流管道以避免有状态。

包java.util.stream,无状态行为

从线程安全性和正确性的角度来看,解决方案 1 没有任何问题。不过,性能(作为并行处理的优势)可能会受到影响。


为什么解决方案 #1 是一个不好的做法?

我不会说这是一种不好的做法或不可接受的事情。出于性能考虑,不建议这样做。

他们只是提到(除非我错过了什么)“可能会发生意想不到的结果”。像什么?

“意外结果”是一个非常广泛的术语,通常指不正确的同步,“刚刚发生了什么?”之类的行为。

在这个例子中会发生这种情况吗?

事实并非如此。您可能不会遇到问题。

如果没有,您能给我提供一个可能发生这种情况的例子吗?

将 更改AtomicInteger为int*,替换number.incrementAndGet()为++number,您将得到一个。


*盒装int(例如基于包装器、基于数组),以便您可以在 lambda 中使用它