使用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%重复输入之间是一致的。