Back to HashMap Internals
Q3Question 3 of 10

What is Collections.synchronizedMap?

Key Concepts
  • Collections.synchronizedMap wraps any Map with a single mutex that synchronizes every method
  • Iteration is NOT automatically synchronized; you must wrap the entire loop in synchronized(map)
  • Compound operations like check-then-act (containsKey + put) are not atomic and still have race conditions
  • ConcurrentHashMap is superior: finer-grained locking, lock-free reads, atomic compound operations, and safe iteration
  • synchronizedMap locks the entire map during iteration, blocking all other threads for the duration of the scan
  • Use synchronizedMap only for quick migration of legacy code; use ConcurrentHashMap for all new multi-threaded code

Definition

Collections.synchronizedMap wraps a HashMap with a single mutex that synchronizes every operation, but iteration still requires manual external synchronization and compound operations are not atomic.
  • Single mutex: every method (get, put, remove, size, containsKey) acquires the same lock object
  • Iteration not synchronized: the iterator is fail-fast; you must wrap iteration in synchronized(map) to prevent CME from concurrent modifications
  • Compound operations not atomic: containsKey + put is two separate locked operations with a gap between them
  • Performance: single lock creates a bottleneck; ConcurrentHashMap's per-bucket locking is 4-10x faster under contention
  • When to use: only for quick thread-safe wrapping of existing HashMap code; prefer ConcurrentHashMap for new code

synchronizedMap is like putting one padlock on an entire filing cabinet. Any operation requires the padlock – safe for single operations. But reading through every folder (iteration) means holding the padlock the entire time – nobody else can read or write until the scan is complete. ConcurrentHashMap is like individual locks per drawer – you can scan drawers while others read and write different drawers.

Understanding the Concepts

What is Collections.synchronizedMap?

Collections.synchronizedMap(new HashMap<>()) returns a wrapper that delegates all operations to the underlying HashMap but wraps each call in a synchronized(mutex) block.

JAVA
package com.bank.demo;

import java.math.BigDecimal;
import java.util.*;

public class SynchronizedMapDemo {
    public static void main(String[] args) {
        Map<String, BigDecimal> balances =
            Collections.synchronizedMap(new HashMap<>());

        // Individual operations are thread-safe
        balances.put("SAV2001", new BigDecimal("75000"));  // synchronized
        balances.get("SAV2001");                            // synchronized

        // WRONG: iteration without external sync
        // for (var entry : balances.entrySet()) { } // CME risk!

        // CORRECT: external synchronization for iteration
        synchronized (balances) {
            for (var entry : balances.entrySet()) {
                System.out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
        // PROBLEM: entire map locked during iteration!
    }
}
TEXT
SAV2001: 75000

Why is iteration dangerous without external sync?

The synchronized wrapper synchronizes individual methods, but the iterator is created and used across multiple method calls. Between iterator creation and iteration completion, another thread can modify the map, causing ConcurrentModificationException.

JAVA
// Thread 1: iterating
for (var entry : synchronizedMap.entrySet()) {
    // Between this iteration step and the next,
    // Thread 2 might call: synchronizedMap.put("NEW", value);
    // → ConcurrentModificationException!
}

// CORRECT: hold the lock for the entire iteration
synchronized (synchronizedMap) {
    for (var entry : synchronizedMap.entrySet()) {
        // Thread 2 cannot modify – it is blocked on the lock
    }
}

Why are compound operations still racy?

Even though each individual method is synchronized, the gap between two calls is not protected:

JAVA
Map<String, BigDecimal> map = Collections.synchronizedMap(new HashMap<>());

// RACE CONDITION:
// Thread 1: if (!map.containsKey("SAV2001")) {    ← lock, check, unlock
// Thread 2: if (!map.containsKey("SAV2001")) {    ← lock, check, unlock (both see absent)
// Thread 1:     map.put("SAV2001", BigDecimal.ZERO); ← lock, put, unlock
// Thread 2:     map.put("SAV2001", BigDecimal.ZERO); ← lock, put, unlock (overwrites!)
// }

// CORRECT: external sync for compound operation
synchronized (map) {
    if (!map.containsKey("SAV2001")) {
        map.put("SAV2001", BigDecimal.ZERO);
    }
}

// BETTER: ConcurrentHashMap atomic operation
ConcurrentHashMap<String, BigDecimal> chm = new ConcurrentHashMap<>();
chm.computeIfAbsent("SAV2001", k -> BigDecimal.ZERO); // atomic!

How does synchronizedMap compare to ConcurrentHashMap?

| Feature | synchronizedMap | ConcurrentHashMap |
|---|---|---|
| Lock granularity | Entire map (one lock) | Per-bucket |
| Read locking | Yes (same lock) | No (volatile reads) |
| Iteration | Needs manual sync | Weakly consistent, safe |
| Compound ops | Not atomic | Atomic (compute, merge) |
| Performance under contention | Poor (bottleneck) | Excellent |
| Null keys/values | Depends on wrapped map | Not allowed |

What should a senior engineer know?

- The mutex object. By default, the mutex is the wrapper itself (this). You can provide a custom mutex: Collections.synchronizedMap(map) uses the returned map as mutex. Iteration must synchronize on this same object.

- Deadlock risk. If you hold the lock on the synchronized map and call another synchronized method that also needs this lock, you get a deadlock. Keep synchronized blocks short.

- Alternatives. For read-heavy maps: ConcurrentHashMap. For copy-on-write semantics: manually implement using volatile + copy. For ordered thread-safe maps: ConcurrentSkipListMap.

What are common interview follow-up questions?

  • "Why do you need synchronized(map) for iteration?" – The iterator spans multiple method calls; individual method sync does not protect the full iteration.
  • "What is the difference between synchronizedMap and ConcurrentHashMap?" – Single lock vs per-bucket, manual iteration sync vs safe iteration.
  • "When would you use synchronizedMap?" – Only for quick migration of legacy HashMap code to thread-safe.

Visual Overview

DIAGRAM
  synchronizedMap vs ConcurrentHashMap

  synchronizedMap (single lock):
  ┌─────────────────────────────────────┐
  │ get("A")  → LOCK map → read → UNLOCK│
  │ put("B")  → LOCK map → write→ UNLOCK│
  │ size()    → LOCK map → read → UNLOCK│
  │ iteration → must LOCK entire scan   │
  │ All threads serialize on ONE lock   │
  └─────────────────────────────────────┘

  ConcurrentHashMap (per-bucket):
  ┌─────────────────────────────────────┐
  │ get("A")  → volatile read (NO lock) │
  │ put("B")  → lock bucket(B) only     │
  │ size()    → approximate (no lock)   │
  │ iteration → weakly consistent (safe)│
  │ Threads proceed in parallel          │
  └─────────────────────────────────────┘

Key Takeaway

Collections.synchronizedMap is the simplest way to make a HashMap thread-safe, but it is also the crudest. Every operation – even get and size – acquires the same global lock. Iteration requires you to hold this lock for the entire scan, blocking all other threads. And compound operations like check-then-act still have race conditions between the two calls.

ConcurrentHashMap is superior in every dimension: reads require no locking, writes lock only the specific bucket, iteration is safe without external synchronization, and compound operations are atomic. The only reason to use synchronizedMap is for a quick migration of existing HashMap code when you need thread safety immediately and cannot refactor to ConcurrentHashMap.

In the National Bank of India's EOD batch process, the balance cache used synchronizedMap. During the report generation scan (iterating 200K entries), the entire map was locked for 3 seconds, blocking all real-time balance lookups. Switching to ConcurrentHashMap eliminated the blocking – the scan ran alongside live reads without interference.

Remember one sentence: synchronizedMap wraps a single lock around every operation, but iteration and compound operations still need manual synchronization – use ConcurrentHashMap instead.

---

Share
Use ← → arrow keys to navigate