Guide to Java ConcurrentMap

ConcurrentMap is an interface of Java Collections Framework that is used to create a thread-safe Map in Java. It stores objects as key and value pairs into the Map but in a synchronized way.

Although we already have HashMap and HashTable in Java, none of these works well in the concurrency context. So, using a concurrent map in the thread-safe application is recommended.

1. How does a Java ConcurrentMap Work?

Internally, ConcurrentMap uses the data segments (shards or partitions) by dividing the map internally into this number of partitions (default is 16). While performing add or update operations by a thread, ConcurrentMap locks that particular segment where the update has to happen. But it allows other threads to read any value from other unlocked segments.

It means we do not need to have synchronized blocks when accessing a ConcurrentMap in multi-threaded applications because data consistency is maintained internally.

In normal applications, a single shard stores a reasonable count of key-value pairs and allows multiple threads to perform read operations. And read performance is also very optimal. The table is dynamically expanded when there are too many collisions.

Note that result of method size(), isEmpty(), and containsValue() reflect transient state of the map and are typically helpful for monitoring or estimation purposes but not for program control.

2. ConcurrentMap Implementations

The following classes implement the ConcurrentMap in Java.

2.1. ConcurrentHashMap

ConcurrentHashMap is an implementation class of the ConcurrentMap and similar to the HashTable except that it stores data into small memory segments to make it available for concurrent threads independently.

By default, it creates 16 segments that can be accessed by concurrent threads and are locked for modifying the records. It uses happens-before concept for updating records. It does not perform any locking for reading operations and provides the latest updated data to the thread.

2.2. ConcurrentSkipListMap

It is an implementation class of ConcurrentMap and ConcurrentNavigableMap. It stores data in either natural sorted order or as specified by the Comparator during its initialization. Its implementation is based on the SkipLists data structure that has overall O(log n) complexity for insertion, deletion and search operations.

Also, note that keys in ConcurrentHashMap are not in sorted order, so for cases when ordering is required, ConcurrentSkipListMap is a better choice. It is a concurrent version of TreeMap. It orders the keys in ascending order by default.

3. ConcurrentMap Operations

Let us learn how to perform various operations on concurrent maps.

3.1. Creating a ConcurrentMap

To use a ConcurrentMap, we can use create an instance of any of its implementation classes. We can call the constructor and pass the required arguments such as initial capacity, load factor, and concurrency level.

  • The default constructor will create an empty ConcurrentMap with an initialCapacity of 16 and a load factor of 0.75f.
  • The loadFactor controls the dense packaging inside the map, further optimizing memory use.
  • The concurrencyLevel controls the number of shards in the map. For example, a concurrency level set to 1 will ensure that only one shard is created and maintained.

Note that these parameters only affect the initial size of the map. They may not be considered during map resizing.

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();

ConcurrentHashMap(int initialCapacity);

ConcurrentHashMap(int initialCapacity, float loadFactor);

ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel);

Pass the existing map to its constructor to initialize a ConcurrentMap with the same entries as a given map.

ConcurrentHashMap(Map<? extends K,? extends V> m)

3.2. Adding Entries

To add elements to a concurrent map, we can use one of the following methods:

  • put(key, value): takes two arguments, first argument is the key and second is the value. Neither the key nor the value can be null.
  • putIfAbsent(key, value): If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value.
  • computeIfAbsent(key, mappingFunction): If the specified key is not already associated with a value, it attempts to compute the value using the given mapping function and enters it into the map unless null. This method is very beneficial when calculating the value is an expensive operation, such as getting the value from a remote system or the database. This method ensures that computation will happen only when the value is not present on the map, thus preventing unnecessary calculations.

For compute… and merge… operations, if the computed value is null then the key-value mapping is removed if present or remains absent if previously absent.

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();

cmap.put(1, "Delhi");
cmap.putIfAbsent(2, "NewYork");
cmap.computeIfAbsent("3", k -> getValueFromDatabase(k));

3.3. Removing Entries

Use the remove() method for removing an entry by its key.

cmap.remove(2);

3.4. Iterating over Entries

To iterate over the keys, values or entries of a ConcurrentMap, we can use a simple for-loop or enhanced for-loop.

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
cmap.put(1, "Delhi");
cmap.put(2, "NewYork");
cmap.put(3, "London");

// Iterating concurrent map keys
for (Integer entry : cmap.keySet()) {
  System.out.println("Entry -- " + entry);
}

// Iterating concurrent map values
for (String value : cmap.values()) {
  System.out.println("Value -- " + value);
}

// Iterating concurrent map entries
for (Map.Entry<Integer, String> entry : cmap.entrySet()) {
  System.out.println(entry.getKey() + " -- " + entry.getValue());
}

ConcurrentMap supports stream operations as well. During bulk operations in Stream, similar to the above iterators, it does not throw ConcurrentModificationException.

Stream.of(cmap.entrySet()).forEach(System.out::println);

3.5. Converting HashMap to ConcurrentMap

To convert a HashMap into ConcurrentMap, use its constructor and pass the hashmap as a constructor argument.

Map<Integer, String> hashmap = ...;

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>(hashmap);

4. Handling Missing Keys in ConcurrentMap

Java added a new method getOrDefault() to its 1.8 version to handle the missing keys. This method returns a default value if the specified key does not exist in the ConcurrentMap.

ConcurrentMap<Integer, String> cmap = new ConcurrentHashMap<>();
cmap.put(1, "Delhi");
cmap.put(2, "NewYork");
cmap.put(3, "London");

String val = cmap.getOrDefault(1,"Bombay");
System.out.println("Value = "+val);       //Prints Delhi

val = cmap.getOrDefault(10, "Kolkata");
System.out.println("Value = "+val);       //Prints Kolkata

5. Conclusion

The ConcurrentMap and its implementations are a good fit for highly concurrent applications. In this tutorial, we learned the initial constructor’s parameters that change the behavior of map instances during concurrent operations.

We also learned to perform various operations on the maps using examples and best practices for optimal performance.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
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.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode