我有一个包含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());
});
Run Code Online (Sandbox Code Playgroud)
我想问一下,通过并行线程填充这样的Map是否安全.难道不可能出现并发问题,并且某些数据可能会在Map中丢失吗?
Tun*_*aki 18
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());
Run Code Online (Sandbox Code Playgroud)
一定要运行几次.操作后打印的地图大小不是10000,这是列表的大小,但稍微少一点,这是一个非常好的机会(并发的乐趣).
这里的解决方案一如既往不使用forEach,而是使用方法和内置的可变缩减方法:collecttoMap
Map<Integer, Integer> map = list.parallelStream().collect(Collectors.toMap(i -> i, i -> i));
Run Code Online (Sandbox Code Playgroud)
使用在上面的示例代码行的代码,你可以放心,地图大小将始终是10000的流API确保它是安全的,收集到非线程安全的容器,即使是在平行.这也意味着你不需要使用toConcurrentMap是安全的,如果你特别想要一个ConcurrentMap结果而不是一般的,那么需要这个收集器Map; 但就线程安全而言collect,您可以同时使用两者.
HashMap不是线程安全的,而是ConcurrentHashMap;改用那个
Map<String, String> map = new ConcurrentHashMap<>();
Run Code Online (Sandbox Code Playgroud)
并且您的代码将按预期工作。
forEach()vs 的性能比较toMap()在 JVM 预热后,使用 1M 元素、使用并行流和使用中值计时,该forEach()版本始终比toMap()版本快 2-3 倍。
结果在完全唯一、25% 重复和 100% 重复输入之间是一致的。