在Java 8中使用parallelstream()填充Map是否安全?



我有一个包含100万个对象的列表,我需要将其填充到Map中。现在,我想减少将其填充到Map中的时间,为此,我计划使用Java 8 parallelstream(),如下所示:

List<Person> list = new LinkedList<>();
Map<String, String> map = new HashMap<>();
list.parallelStream().forEach(person ->{
    map.put(person.getName(), person.getAge());
});

我想问这样通过并行线程填充Map是否安全。是否可能存在并发性问题,并且可能在Map中丢失一些数据?

使用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确保将其收集到非线程安全的容器中是安全的,即使是并行的。这也意味着您不需要使用toConcurrentMap来保证安全,如果您特别想要ConcurrentMap作为结果,则需要此收集器,而不是一般的Map;但就collect的线程安全而言,您可以同时使用。

HashMap不是线程安全的,但ConcurrentHashMap是;用那个代替

Map<String, String> map = new ConcurrentHashMap<>();

和你的代码将按预期工作。


forEach()toMap()性能比较

在JVM预热后,使用1M元素,使用并行流并使用中位数计时,forEach()版本始终比toMap()版本快2-3倍。

结果在完全唯一、25%重复和100%重复输入之间是一致的。

最新更新