Collections.synchronizedMap与ConcurrentHashMap的比较
1.概述
在本教程中,我们将讨论Collections.synchronizedMap()和ConcurrentHashMap之间的区别。
此外,我们还将看看每个读取和写入操作的性能输出。
2.差异
Collections.synchronizedMap()和ConcurrentHashMap都提供了对数据集合的线程安全操作。
Collections实用类提供了多态的算法,对集合进行操作并返回包装好的集合。它的 synchronizedMap()方法提供了线程安全的功能。
顾名思义,synchronizedMap()返回一个由我们在参数中提供的Map支持的同步Map。为了提供线程安全,synchronizedMap()允许通过返回的Map对支持的Map进行所有访问。
ConcurrentHashMap是在JDK 1.5中引入的,作为HashMap的增强版,支持高并发的检索和更新。HashMap并不是线程安全的,所以它可能会在线程争用时导致不正确的结果。
ConcurrentHashMap类是线程安全的。因此,多个线程可以对一个对象进行操作,而不会产生任何问题。
在ConcurrentHashMap中,读操作是无阻塞的,而写操作则需要在特定的段或桶上加锁。默认的桶或并发级别是16,这意味着16个线程在对一个段或桶加锁后可以在任何瞬间进行写入。
2.1. ConcurrentModificationException
对于像HashMap这样的对象,执行并发操作是不允许的。因此,如果我们试图在迭代一个HashMap的时候更新它,我们会收到一个ConcurrentModificationException。在使用synchronizedMap()时也会出现这种情况。
@Test(expected = ConcurrentModificationException.class)
public void whenRemoveAndAddOnHashMap_thenConcurrentModificationError() {
Map<Integer, String> map = new HashMap<>();
map.put(1, "baeldung");
map.put(2, "HashMap");
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(map);
Iterator<Entry<Integer, String>> iterator = synchronizedMap.entrySet().iterator();
while (iterator.hasNext()) {
synchronizedMap.put(3, "Modification");
iterator.next();
}
}
然而,对于ConcurrentHashMap来说,情况并非如此。
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "baeldung");
map.put(2, "HashMap");
Iterator<Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
map.put(3, "Modification");
iterator.next()
}
Assert.assertEquals(3, map.size());
2.2.null支持
Collections.synchronizedMap()和ConcurrentHashMap 对null键和值的处理方式不同。
ConcurrentHashMap不允许在键或值中出现null。
@Test(expected = NullPointerException.class)
public void allowNullKey_In_ConcurrentHasMap() {
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put(null, 1);
}
然而,当使用Collections.synchronizedMap()时,null的支持取决于输入Map。当Collections.synchronizedMap()被HashMap或LinkedHashMap支持时,我们可以有一个null作为键和任意数量的null值,而如果我们使用TreeMap,我们可以有null值,但没有null键。
让我们断言,我们可以为Collections.synchronizedMap()支持的HashMap使用一个null的键。
Map<String, Integer> map = Collections
.synchronizedMap(new HashMap<String, Integer>());
map.put(null, 1);
Assert.assertTrue(map.get(null).equals(1));
同样,我们可以验证null对Collections.synchronizedMap()和ConcurrentHashMap的值的支持。
3.性能比较
让我们来比较一下ConcurrentHashMap与Collections.synchronizedMap()的性能。在这种情况下,我们使用开源框架Java Microbenchmark Harness(JMH)来比较方法的性能,以纳秒为单位。
我们对这些地图的随机读和写操作进行了比较。让我们快速看一下我们的JMH基准代码。
@Benchmark
public void randomReadAndWriteSynchronizedMap() {
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
performReadAndWriteTest(map);
}
@Benchmark
public void randomReadAndWriteConcurrentHashMap() {
Map<String, Integer> map = new ConcurrentHashMap<>();
performReadAndWriteTest(map);
}
private void performReadAndWriteTest(final Map<String, Integer> map) {
for (int i = 0; i < TEST_NO_ITEMS; i++) {
Integer randNumber = (int) Math.ceil(Math.random() * TEST_NO_ITEMS);
map.get(String.valueOf(randNumber));
map.put(String.valueOf(randNumber), randNumber);
}
}
我们使用10个线程对1000个项目进行了5次迭代的性能基准测试。让我们看看基准测试的结果。
Benchmark Mode Cnt Score Error Units
MapPerformanceComparison.randomReadAndWriteConcurrentHashMap avgt 100 3061555.822 ± 84058.268 ns/op
MapPerformanceComparison.randomReadAndWriteSynchronizedMap avgt 100 3234465.857 ± 60884.889 ns/op
MapPerformanceComparison.randomReadConcurrentHashMap avgt 100 2728614.243 ± 148477.676 ns/op
MapPerformanceComparison.randomReadSynchronizedMap avgt 100 3471147.160 ± 174361.431 ns/op
MapPerformanceComparison.randomWriteConcurrentHashMap avgt 100 3081447.009 ± 69533.465 ns/op
MapPerformanceComparison.randomWriteSynchronizedMap avgt 100 3385768.422 ± 141412.744 ns/op
上述结果表明,ConcurrentHashMap比Collections.synchronizedMap()表现更好。
4.何时使用
当数据的一致性是最重要的时候,我们应该选择Collections.synchronizedMap(),而对于那些写操作远多于读操作的性能关键型应用,我们应该选择ConcurrentHashMap。
这是因为Collections.synchronizedMap()要求每个线程在读/写操作中都要获得整个对象的锁。相比之下,ConcurrentHashMap 允许线程在集合的不同部分获得锁,并同时进行修改。
5.总结
在这篇文章中,我们展示了ConcurrentHashMap和Collections.synchronizedMap()之间的区别。我们还用一个简单的JMH基准测试展示了它们的性能。
像往常一样,代码样本可以在GitHub上获得。