【发布时间】:2018-11-16 00:37:33
【问题描述】:
假设我有这份水果清单:-
List<String> f = Arrays.asList("Banana", "Apple", "Grape", "Orange", "Kiwi");
我需要在每个水果前面加上一个序列号并打印出来。水果的顺序或序列号无关紧要。所以这是一个有效的输出:-
4. Kiwi
3. Orange
1. Grape
2. Apple
5. Banana
解决方案 #1
AtomicInteger number = new AtomicInteger(0);
String result = f.parallelStream()
.map(i -> String.format("%d. %s", number.incrementAndGet(), i))
.collect(Collectors.joining("\n"));
解决方案 #2
String result = IntStream.rangeClosed(1, f.size())
.parallel()
.mapToObj(i -> String.format("%d. %s", i, f.get(i - 1)))
.collect(Collectors.joining("\n"));
问题
为什么解决方案 #1 是不好的做法?我在很多地方都看到基于AtomicInteger 的解决方案不好(比如this answer),特别是在并行流处理中(这就是我在上面使用并行流来尝试遇到问题的原因)。
我查看了这些问题/答案:-
In which cases Stream operations should be stateful?
Is use of AtomicInteger for indexing in Stream a legit way?
Java 8: Preferred way to count iterations of a lambda?
他们只是提到(除非我错过了什么)“可能会出现意想不到的结果”。像什么?在这个例子中会发生吗?如果没有,你能举个例子吗?
至于“不保证映射器函数的应用顺序”,嗯,这就是并行处理的本质,所以我接受它,而且顺序不在这个特定的例子中无关紧要。
AtomicInteger 是线程安全的,所以在并行处理中应该不是问题。
有人可以提供示例,在哪些情况下使用这种基于状态的解决方案会出现问题?
【问题讨论】:
-
tldr: 副作用是“讨厌的”,即使是“线程安全的”。排序参数在更一般的情况下非常相关:例如。而不是添加整数(x + y == y + x),如果它是连接字符串(concat(x,y)!= concat(y,x))怎么办?如果避免了副作用,意外引入这种情况的机会就会大大减少。
-
从文档中不清楚是需要无国籍还是只推荐无国籍。我个人认为 #1 没有问题,但我可以想象 #2 表现更好。
-
流来自函数式编程,理想情况下应该没有副作用。有时这是不可能的,但如果有一种简单的方法可以在没有副作用的情况下实现相同的目标,那么您应该使用它。
-
好吧,当你对数字既不是顺序也不是反映源元素顺序的结果很好,唯一剩下的问题是它效率低下,比较到推荐的方法。但是大多数其他问答都是关于任务不适合这样错误的顺序,好吧,当你在这些罕见的情况下使用这种模式时,你可以接受无意义的数字,它可能很快就会成为一种习惯......
标签: java java-8 thread-safety java-stream atomicinteger