Collections.synchronizedMap vs. ConcurrentHashMap – Collections.synchronizedMap vs. ConcurrentHashMap

最后修改: 2020年 12月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll discuss the differences between Collections.synchronizedMap() and ConcurrentHashMap.

在本教程中,我们将讨论Collections.synchronizedMap()ConcurrentHashMap之间的区别。

Additionally, we’ll look at the performance outputs of the read and write operations for each.

此外,我们还将看看每个人的读和写操作的性能输出。

2. The Differences

2.差异

Collections.synchronizedMap() and ConcurrentHashMap both provide thread-safe operations on collections of data.

Collections.synchronizedMap()ConcurrentHashMap都提供了对数据集合的线程安全操作。

The Collections utility class provides polymorphic algorithms that operate on collections and return wrapped collections. Its synchronizedMap() method provides thread-safe functionality.

Collections实用类提供了多态算法,对集合进行操作并返回封装好的集合。其 synchronizedMap()方法提供了线程安全的功能。

As the name implies, synchronizedMap() returns a synchronized Map backed by the Map that we provide in the parameter. To provide thread-safety, synchronizedMap() allows all accesses to the backing Map via the returned Map.

顾名思义,synchronizedMap()返回一个由我们在参数中提供的Map支持的同步Map。为了提供线程安全,synchronizedMap()允许通过返回的Map对支持的Map的所有访问。

ConcurrentHashMap was introduced in JDK 1.5 as an enhancement of HashMap that supports high concurrency for retrievals as well as updates. HashMap isn’t thread-safe, so it might lead to incorrect results during thread contention.

ConcurrentHashMap是在JDK 1.5中引入的,作为HashMap的增强版,支持高并发的检索和更新。HashMap不是线程安全的,所以它可能会在线程争用时导致不正确的结果。

The ConcurrentHashMap class is thread-safe. Therefore, multiple threads can operate on a single object with no complications.

ConcurrentHashMap类是线程安全的。因此,多个线程可以对一个对象进行操作,而不会产生任何问题。

In ConcurrentHashMap, read operations are non-blocking, whereas write operations take a lock on a particular segment or bucket. The default bucket or concurrency level is 16, which means 16 threads can write at any instant after taking a lock on a segment or bucket.

ConcurrentHashMap中,读操作是无阻塞的,而写操作则需要锁定一个特定的段或桶。默认的桶或并发级别是16,这意味着16个线程在锁定一个段或桶后可以在任何瞬间写入。

2.1. ConcurrentModificationException

2.1.ConcurrentModificationException

For objects like HashMap, performing concurrent operations is not allowed. Therefore, if we try to update a HashMap while iterating over it, we will receive a ConcurrentModificationException. This will also occur when using synchronizedMap():

对于像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();
    }
}

However, this is not the case with ConcurrentHashMap:

然而,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 Support

2.2. null支持

Collections.synchronizedMap() and ConcurrentHashMap handle null keys and values differently.

Collections.synchronizedMap()ConcurrentHashMap null键和值的处理方式不同

ConcurrentHashMap doesn’t allow null in keys or values:

ConcurrentHashMap不允许键和值中出现null

@Test(expected = NullPointerException.class)
public void allowNullKey_In_ConcurrentHasMap() {
    Map<String, Integer> map = new ConcurrentHashMap<>();
    map.put(null, 1);
}

However, when using Collections.synchronizedMap(), null support depends on the input Map. We can have one null as a key and any number of null values when Collections.synchronizedMap() is backed by HashMap or LinkedHashMap, whereas if we’re using TreeMap, we can have null values but not null keys.

然而,当使用Collections.synchronizedMap()时,null支持取决于输入MapCollections.synchronizedMap()HashMapLinkedHashMap支持时,我们可以有一个null作为键和任意数量的null值,如果我们使用TreeMap,我们可以有null值但没有null键。

Let’s assert that we can use a null key for Collections.synchronizedMap() backed by a HashMap:

让我们断言,我们可以为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));

Similarly, we can validate null support in values for both Collections.synchronizedMap() and ConcurrentHashMap.

同样,我们可以验证nullCollections.synchronizedMap()ConcurrentHashMap值的支持。

3. Performance Comparison

3.性能比较

Let’s compare the performances of ConcurrentHashMap versus Collections.synchronizedMap(). In this case, we’re using the open-source framework Java Microbenchmark Harness (JMH) to compare the performances of the methods in nanoseconds.

让我们来比较一下ConcurrentHashMapCollections.synchronizedMap().的性能。在这种情况下,我们使用开源框架Java Microbenchmark Harness(JMH)来比较这些方法在纳秒级的性能。

We ran the comparison for random read and write operations on these maps. Let’s take a quick look at our JMH benchmark code:

我们对这些地图的随机读和写操作进行了比较。让我们快速看一下我们的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);
    }
}

We ran our performance benchmarks using 5 iterations with 10 threads for 1,000 items. Let’s see the benchmark results:

我们使用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

The above results show that ConcurrentHashMap performs better than Collections.synchronizedMap().

上述结果表明,ConcurrentHashMapCollections.synchronizedMap()表现得更好。

4. When to Use

4.何时使用

We should favor Collections.synchronizedMap() when data consistency is of utmost importance, and we should choose ConcurrentHashMap for performance-critical applications where there are far more write operations than there are read operations.

当数据一致性至关重要时,我们应该选择Collections.synchronizedMap(),而对于写操作远多于读操作的性能关键型应用,我们应该选择ConcurrentHashMap

This is because the Collections.synchronizedMap() requires each thread to acquire a lock on the entire object for both read/write operations. By comparison, the ConcurrentHashMap allows threads to acquire locks on separate segments of the collection, and make modifications at the same time.

这是因为Collections.synchronizedMap()要求每个线程在读/写操作中都要获取整个对象的一个锁。相比之下,ConcurrentHashMap允许线程在集合的不同片段上获取锁,并同时进行修改。

5. Conclusion

5.结论

In this article, we’ve demonstrated the differences between ConcurrentHashMap and Collections.synchronizedMap(). We’ve also shown the performances of both of them using a simple JMH benchmark.

在这篇文章中,我们展示了ConcurrentHashMapCollections.synchronizedMap()之间的区别。我们还用一个简单的JMH基准测试展示了它们的性能。

As always, the code samples are available over on GitHub.

像往常一样,代码样本可在GitHub上获得