ConcurrentHashMap vs Collections.synchronizedMap
ConcurrentHashMap is a thread-safe HashMap optimized for concurrent reads and fine-grained writes. Collections.synchronizedMap() wraps any Map with a synchronized keyword on every method.
synchronizedMap = one giant padlock on the entire warehouse door (everyone waits). ConcurrentHashMap = individual locks on each aisle -- shoppers in different aisles never block each other.
synchronizedMap() locks the entire map for every operation — only one thread can read or write at a time. ConcurrentHashMap (Java 8+) uses CAS for empty buckets and synchronized on the first node of a bin for non-empty buckets — effectively per-bucket locking, allowing many threads to read and write different buckets concurrently. ConcurrentHashMap does NOT allow null keys or values (throws NPE). Aggregate operations (putIfAbsent, compute, computeIfAbsent, merge) are atomic in ConcurrentHashMap. size() is an estimate under concurrent modification.
Java 7 ConcurrentHashMap used explicit ReentrantLock-based segments (default 16 segments). Java 8 removed segments entirely — the implementation now uses the array of bins directly with CAS + synchronized, reducing memory overhead and improving throughput. The compute() family of methods is critical for correct atomic updates: map.computeIfAbsent(key, k -> new ArrayList<>()).add(value) is atomic; the equivalent get()+put() is not. LongAdder-based size counter. Under Java 21 virtual threads, ConcurrentHashMap's synchronized blocks can still pin carrier threads — Loom-aware alternatives are being explored.
ConcurrentHashMap achieves high concurrency via CAS for empty-bucket puts and synchronized on only the bucket head for occupied buckets — multiple threads write different buckets simultaneously. synchronizedMap wraps every operation in a single lock, serializing all access. Prefer ConcurrentHashMap for concurrent use. The gotcha: iterating a synchronizedMap still requires external locking on the map object; ConcurrentHashMap's iterators are weakly consistent (reflect state at some point during iteration, no ConcurrentModificationException).
ConcurrentHashMap.size() does not reflect a snapshot — under concurrent modification it returns an approximation. Conditional logic like if (map.size() == 0) ... is a race condition; use map.isEmpty() or isEmpty() inside a compute() for correctness.