Java Collections.synchronizedMap() vs ConcurrentHashMap

Java Collections.synchronizedMap() and ConcurrentHashMap provide thread-safe Map implementations to be used in a concurrent application. In this tutorial, we are going to focus on the core differences between Collections.synchronizedMap() and ConcurrentHashMap.

1. Introduction

ConcurrentHashMap class extends AbstractMap class and implements ConcurrentMap interface and was introduced in Java version 1.5. The HashMap class is not synchronized and can cause data inconsistency when multiple threads use it concurrently. ConcurrentHashMap is a thread-safe alternative of HashMap class.

// ConcurrentHashMap class declaration
public class ConcurrentHashMap <K, V> extends AbstractMap<K, V> implements Concurrent Map<K, V>, Serializable

The synchronizedMap() is a method in Collections class and was introduced in Java version 1.2. It returns a synchronized Map backed by the specified Map that we provide in the parameter.

// synchronizedMap() method
public static Map<K, V> synchronizedMap(Map<K, V> map)

2. Differences between Collections.synchronizedMap() and ConcurrentHashMap

Let’s discuss some of the main differences between the two constructs.

2.1. Locking and Concurrency

Internally. ConcurrentHashMap uses internal segmentation called buckets. It stores all key-value pairs in these buckets. By default, it maintains 16 buckets.

ConcurrentHashMap allows non-blocking read access to all threads, but a thread operating must acquire a lock on a particular bucket before writing the data into it. Note that it does not need to lock the entire map object, so multiple threads can concurrently write as well.

A synchronized map, created using synchronizedMap(), allows serial access to the backing Map thus only one thread can access the Map at a time.

2.2. Null Keys and Values

ConcurrentHashMap internally uses HashTable as the underlying data structure; hence, it doesn’t allow null as a key or a value.

When using synchronizedMap(), null support is decided by the backing map. If the backing map is a HashMap or a LinkedHashMap then we can insert one null key and any number of null values in the map. But if the backing map is a TreeMap then we can’t insert null key, but we can still have any number of null values.

// Putting null in ConcurrentHashMap
ConcurrentHashMap<String, String> chmap = new ConcurrentHashMap<>();
chmap.put(null, “value”);      // NullPointerException
chmap.put(“key”, null);        // NullPointerException

// Putting null in SynchronizedMap obtained from synchronizedMap()
Map<String, Integer> hmap = Collections.synchronizedMap(new HashMap<String, Integer>()); 
hmap.put(null, 1);            // Allowed

Map<String, Integer> tmap = Collections.synchronizedMap(new TreeMap<String, Integer>()); 
tmap.put(null, 1);          // NullPointerException

2.3. ConcurrentModificationException

ConcurrentHashMap doesn’t throw ConcurrentModificationException, so we modify the map entries during the iteration of the map then the iterator doesn’t throw this exception. The iterator of ConcurrentHashMap is fail-safe as it won’t raise ConcurrentModificationException.

The Iterator obtained from synchronizedMap() is fail-fast and raises ConcurrentModificationException.

2.4. Element Ordering

ConcurrentHashMap does not maintain any order for its elements i.e. it won’t provide any guarantee that the element inserted first in the map will print first during the iteration of the map.

Collections.synchronizedMap() is backed by the specified map and preserves the order of the backing map. If we pass HashMap to it, then the order is not preserved, but if we pass a TreeMap, it preserves the order of elements as there in the TreeMap.

2.5. Performance

  • On ConcurrentHashMap instance, multiple threads can operate simultaneously in order to perform concurrent read and write operations in a safe manner and because of this ConcurrentHashMap performs better than other synchronized maps.
  • Collections.synchronizedMap() provides serial access i.e. allows only one thread to operate on it for either read or write operation. This increases the waiting time of other threads and relatively reduces the performance of the map.

Let’s compare the performance of both of these using Java Microbenchmark Harness(JMH). We ran our performance benchmarks using 1 iteration for 1000 items.

// Creating BenchmarkRunner class
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 1, warmups = 1)
public class BenchmarkRunner 
{

   // For running the Benchmarks
   public static void main(String[] args) throws RunnerException
   {
		Options opt = new OptionsBuilder()
                .include(BenchmarkRunner.class.getSimpleName())
                .forks(1)
                .build();
             new Runner(opt).run();
   }

    // Creating Benchmark for synchronizedMap
    @Benchmark
    public void synchronizedMapReadAndWrite() 
   {
    	Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
    	readAndWriteTest(map);
    }
    
     // Creating Benchmark for ConcurrentHashMap
    @Benchmark
    public void concurrentHashMapReadAndWrite() 
   {
        Map<String, Integer> map = new ConcurrentHashMap<>();
        readAndWriteTest(map);
   }

    // Test method for putting and getting random numbers from the map
    private void readAndWriteTest(final Map<String, Integer> map) 
   {
        for (int i = 0; i < 1000; i++) {
            Integer randomNumber = (int) Math.ceil(Math.random() * 1000);
            map.get(String.valueOf(randomNumber));
            map.put(String.valueOf(randomNumber), randomNumber);
        }
   }
}
Benchmark                                                                        Mode   Cnt   Score    Error   Units
concurrentHashMapReadAndWrite                            avgt         5          0.146          0.047       ms/op
synchronizedMapReadAndWrite                                 avgt         5          0.307         0.014        ms/op

Look at the Benchmark stats, clearly, ConcurrentHashMap performs better for both read and write operations than synchronizedMap().

3. Usecases

Serial access provided by Collections.synchronizedMap() guarantees the required data consistency if there are more threads to write and only a few threads are reading the data. So, we should use a synchronized map when performance is not the primary concern, rather data consistency is.

In a performance-critical application, we should use ConcurrentHashMap for its better runtime performance. Also, if there are only a few threads performing the writes and the remaining threads are reading the data, then using ConcurrentHashMap makes more sense.

Also, we can use ConcurrentHashMap instead of HashTable in a multithreaded environment as the latter is a legacy class and no longer recommended.

4. Conclusion

In this post, we have seen the key differences between ConcurrentHashMap and Collections.synchronizedMap() and on which factors we can decide between the two while using them in our code.

Happy Learning !!

Leave a Reply

0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.