Java ConcurrentHashMap


In concurrent programming, ensuring thread safety is crucial when multiple threads interact with shared data structures. The ConcurrentHashMap class in Java, part of the java.util.concurrent package, provides a high-performance, thread-safe implementation of the ConcurrentMap interface. It allows multiple threads to read and write to the map concurrently without the risk of data corruption, all while maintaining efficient performance.

This article explores the ConcurrentHashMap class, its key features, and how to use it effectively for building multi-threaded applications in Java.


What is ConcurrentHashMap in Java?

ConcurrentHashMap is a thread-safe map implementation that allows concurrent access to different segments of the map without the need for global synchronization. It is part of the java.util.concurrent package and is designed to handle large-scale concurrent access, making it ideal for applications with multiple threads accessing and modifying a shared map.

Unlike traditional synchronized maps (like HashMap wrapped with Collections.synchronizedMap()), ConcurrentHashMap provides higher concurrency and better performance because it allows multiple threads to access different segments of the map simultaneously.

Key Features of ConcurrentHashMap

  • Thread Safety: Ensures that map operations are thread-safe without needing explicit synchronization.
  • Concurrency Level: Splits the map into segments, and each segment can be locked separately, allowing multiple threads to work in parallel.
  • Efficient Read Operations: Allows concurrent reads without locking the entire map.
  • Atomic Operations: Supports atomic operations such as putIfAbsent(), remove(), replace(), and computeIfAbsent(), preventing race conditions.
  • No Null Keys/Values: Like other Map implementations, ConcurrentHashMap does not allow null keys or values.

Core Methods of ConcurrentHashMap

The ConcurrentHashMap class extends AbstractMap and implements ConcurrentMap. Some of its important methods include:

1. putIfAbsent(K key, V value)

This method adds a key-value pair to the map only if the specified key is not already present.

V putIfAbsent(K key, V value);

2. remove(Object key, Object value)

Removes the entry for a specific key only if it is currently mapped to the specified value.

boolean remove(Object key, Object value);

3. replace(K key, V oldValue, V newValue)

Replaces the value associated with a key only if the current value is equal to the expected oldValue.

boolean replace(K key, V oldValue, V newValue);

4. replace(K key, V value)

Replaces the value associated with the specified key, if the key is already present.

V replace(K key, V value);

5. computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

This method computes the value for the specified key using the provided function only if the key is not already present in the map.

V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);

6. computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

This method computes the value for the specified key using the provided function only if the key is already present in the map.

V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

7. compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

This method computes a new value for the specified key using the provided function.

V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

8. forEach(BiConsumer<? super K, ? super V> action)

This method iterates over all the key-value pairs in the map and performs the given action on each entry.

void forEach(BiConsumer<? super K, ? super V> action);

Example: Using ConcurrentHashMap

Here’s an example demonstrating how to use ConcurrentHashMap in a multi-threaded Java application:

import java.util.concurrent.*;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        // Create a ConcurrentHashMap
        ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();

        // Add entries using putIfAbsent (atomic operation)
        map.putIfAbsent("Apple", 3);
        map.putIfAbsent("Banana", 2);
        map.putIfAbsent("Mango", 1);

        // Print the map
        System.out.println("Map after adding entries: " + map);

        // Replace an entry if present
        map.replace("Apple", 5);
        System.out.println("Map after replacing Apple: " + map);

        // Compute a value if the key is absent
        map.computeIfAbsent("Orange", key -> 4);
        System.out.println("Map after computing value for Orange: " + map);

        // Compute a value if the key is present
        map.computeIfPresent("Banana", (key, value) -> value * 2);
        System.out.println("Map after computing value for Banana: " + map);

        // Remove an entry if it matches the expected value
        map.remove("Mango", 1);
        System.out.println("Map after removing Mango: " + map);

        // Iterate over all entries using forEach
        map.forEach((key, value) -> System.out.println(key + ": " + value));
    }
}

Output:

Map after adding entries: {Apple=3, Banana=2, Mango=1}
Map after replacing Apple: {Apple=5, Banana=2, Mango=1}
Map after computing value for Orange: {Apple=5, Banana=2, Mango=1, Orange=4}
Map after computing value for Banana: {Apple=5, Banana=4, Mango=1, Orange=4}
Map after removing Mango: {Apple=5, Banana=4, Orange=4}
Apple: 5
Banana: 4
Orange: 4

Explanation:

  • putIfAbsent() adds an entry only if the key does not already exist.
  • replace() updates the value associated with a key if it is present.
  • computeIfAbsent() computes a value for a key if the key is not already in the map.
  • computeIfPresent() computes a value for a key if it already exists.
  • remove() removes an entry only if the current value matches the specified value.
  • forEach() iterates over all entries in a thread-safe manner.

How Does ConcurrentHashMap Achieve Thread-Safety?

ConcurrentHashMap achieves thread safety using a technique called bucket-based concurrency. The map is divided into several segments, and each segment can be locked independently, allowing for high concurrency and performance. Specifically:

  • The map is internally divided into buckets (also referred to as segments).
  • Read operations are typically not locked, allowing multiple threads to read from different segments simultaneously.
  • Write operations (e.g., put(), remove()) lock only the segment where the modification happens, allowing other threads to work on different segments concurrently.

This approach helps to minimize the contention between threads, making ConcurrentHashMap highly efficient in a multi-threaded environment.


When to Use ConcurrentHashMap

Use ConcurrentHashMap in Java when:

  1. Multiple Threads Access the Map: When multiple threads need to read from or modify a shared map concurrently, and thread safety is required.
  2. High Concurrency and Performance: For high-concurrency applications, like caching systems, session management, or real-time data processing.
  3. Atomic Operations: When atomic operations such as putIfAbsent(), replace(), and compute() are needed to avoid race conditions.

ConcurrentHashMap vs HashMap

Here’s a comparison of ConcurrentHashMap and HashMap:

Feature ConcurrentHashMap HashMap
Thread Safety Thread-safe for concurrent operations. Not thread-safe; needs synchronization.
Null Keys/Values Does not allow null keys or values. Allows null keys and values.
Concurrency Allows concurrent reads and writes with multiple threads. Not designed for multi-threaded use.
Performance Higher performance in multi-threaded environments. Suitable for single-threaded environments.