Criteria SynchronizedMap ConcurrentHashMap
Thread Safety Thread-safe. Every individual operation (like get, put) is synchronized. Thread-safe. Allows concurrent read and write operations without locking the entire map.
Lock Level Locks the entire map for every single operation. Uses bucket-level locks, allowing multiple threads to read and update the map without blocking.
Performance Slower due to coarse-grained locking. Generally faster in multi-threaded scenarios due to fine-grained locking and lock stripping.
Iterator Behavior Iterators are fail-fast and may throw a ConcurrentModificationException if the map is modified while being iterated. Iterators are weakly consistent and won’t throw ConcurrentModificationException. They reflect the state of the map at the time the iterator was created.
Null Keys/Values Allows one null key and multiple null values (depends on the underlying map implementation). Does not allow null keys or values.
Use-case Suitable for scenarios with less concurrent modification operations, or where map access isn’t a primary bottleneck. Suitable for high concurrency scenarios where multiple threads might need to modify the map concurrently.
Scalability Less scalable due to global locking. Highly scalable due to segment-level locking.

Example: Difference Between SynchronizedMap and ConcurrentHashMap in Java

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SynchronizedMapVsConcurrentHashMapDemo {

    public static void main(String[] args) throws InterruptedException {
        // Using a SynchronizedMap
        Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
        synchronizedMap.put("Key1", "Value1");

        // Using a ConcurrentHashMap
        Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put("Key1", "Value1");

        // Illustrate concurrent access with SynchronizedMap
        Thread thread1 = new Thread(() -> {
            synchronized (synchronizedMap) {
                synchronizedMap.put("Key2", "Value2");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (synchronizedMap) {
                synchronizedMap.put("Key3", "Value3");
            }
        });

        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("SynchronizedMap contents: " + synchronizedMap);

        // Illustrate concurrent access with ConcurrentHashMap
        Thread thread3 = new Thread(() -> {
            concurrentHashMap.put("Key2", "Value2");
        });

        Thread thread4 = new Thread(() -> {
            concurrentHashMap.put("Key3", "Value3");
        });

        thread3.start();
        thread4.start();
        thread3.join();
        thread4.join();
        System.out.println("ConcurrentHashMap contents: " + concurrentHashMap);
    }
}

Output:

SynchronizedMap contents: {Key1=Value1, Key2=Value2, Key3=Value3}
ConcurrentHashMap contents: {Key1=Value1, Key2=Value2, Key3=Value3}

Explanation:

1. SynchronizedMap: This is a thread-safe version of a regular map achieved using the Collections.synchronizedMap() method. It provides a basic synchronization mechanism which means every individual method (put, get, etc.) is synchronized. If multiple threads try to modify it simultaneously, they must wait for others to complete. For example, while one thread is performing a put operation, no other thread can access the map until the first one is done. This can lead to performance issues.

2. ConcurrentHashMap: This provides thread safety without synchronizing the whole map. Instead, it uses a segment-level lock, allowing multiple threads to read and write without blocking. This leads to better performance under high concurrency.

3. In the provided example:

– For SynchronizedMap: We spawn two threads trying to put values in the map. To safely do concurrent modifications, we need to synchronize on the synchronizedMap itself. If we forget to do so, we could face concurrency issues.

– For ConcurrentHashMap: We spawn two threads trying to put values in the map. Notice that we don't need to provide any external synchronization. The map takes care of concurrent accesses internally.

4. While the output seems similar, the difference lies in the underlying implementation and the potential for scalability. ConcurrentHashMap is preferred when you expect a lot of concurrent reads/writes as it provides finer-grained locking and better performance.