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

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

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

我想问一下,通过并行线程填充这样的Map是否安全。 难道不可能出现并发问题,并且某些数据可能会在Map中丢失吗?

使用parallelStream()收集 HashMap是非常安全的。 但是,使用parallelStream()forEach和消费者向HashMap添加内容是不安全的。

HashMap不是同步类,并且尝试同时将元素放入其中将无法正常工作。 这就是forEach将要做的事情,它将调用给定的使用者,它可以同时从多个线程将元素放入HashMap 。 如果您想要一个简单的代码来演示问题:

 List list = IntStream.range(0, 10000).boxed().collect(Collectors.toList()); Map 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 map = list.parallelStream().collect(Collectors.toMap(i -> i, i -> i)); 

在上面的示例代码中使用该行代码,您可以放心,映射大小始终为10000. Stream API确保即使并行也可以安全地收集到非线程安全容器中。 这也意味着您不需要使用toConcurrentMap是安全的,如果您特别想要ConcurrentMap作为结果,而不是一般Map ,则需要此收集器; 但就线程安全而言,关于collect ,你可以使用两者。

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

 Map map = new ConcurrentHashMap<>(); 

并且您的代码将按预期工作。


forEach()toMap()性能比较

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

结果在所有独特的,25%重复和100%重复输入之间是一致的。