使用
parallelStream()
将数据
收集到
HashMap
中非常安全。但是,使用
parallelStream()
、
forEach
和一个向
HashMap
添加内容的消费者是不安全的。
HashMap
不是一个同步类,尝试并发地向其中放置元素将不能正常工作。这就是
forEach
所做的,它将从多个线程中调用给定的消费者,该消费者会把元素放入
HashMap
中,可能同时进行。如果您想要一个简单的演示问题的代码:
List<Integer> list = IntStream.range(0, 10000).boxed().collect(Collectors.toList());
Map<Integer, Integer> map = new HashMap<>();
list.parallelStream().forEach(i -> {
map.put(i, i);
});
System.out.println(list.size());
System.out.println(map.size());
请确保运行几次。并发的乐趣在于,操作后打印出来的地图大小很有可能不是10000(列表的大小),而是稍微小一些。
解决方案,一如既往,不是使用forEach
,而是使用可变规约方法和内置的collect
方法以及toMap
:
Map<Integer, Integer> map = list.parallelStream().collect(Collectors.toMap(i -> i, i -> i))
使用上面示例代码中的那行代码,您可以放心地确保地图大小始终为10000。Stream API 确保即使在并行情况下收集到非线程安全容器也是安全的
(链接1)。这也意味着您不需要使用
toConcurrentMap
来确保安全,此收集器仅在您特别需要
ConcurrentMap
作为结果而不是一般
Map
时才需要;但就涉及到与
collect
相关的线程安全性而言,两者都可以使用。
equals
和hashCode
方法上。唯一可能导致map操作成为瓶颈的解释是恶意的不良hashcode(例如总是返回1)。再次强调,这不是HashMap
的问题。 - Marko Topolnik