What is ConcurrentHashMap, and when would you choose it over a HashMap or a Collections.synchronizedMap?
answer
- HashMap = not thread-safe; synchronizedMap = one lock for everything
- CHM: lock-free reads, per-bin write locks
- Iterators weakly consistent, never throw CME
- Pick CHM for shared maps wanting throughput
- Single-op atomic, not multi-op atomic
basics
~20 sConcurrentHashMap is a thread-safe map that many threads can read and write at once safely. Use it instead of HashMap (not thread-safe) or a synchronized map (locks the whole map) when several threads share a map.
solid answer
~40 sConcurrentHashMap is a hash map designed for concurrent access. A plain HashMap is not thread-safe: concurrent writes can corrupt it (lost updates, infinite loops in old versions). Collections.synchronizedMap wraps a map with a single lock, so every read and write is serialized through one monitor, which scales poorly. ConcurrentHashMap instead allows reads with no locking at all and uses fine-grained locking on writes (only the affected bucket is locked), so many threads proceed in parallel. Choose it whenever a map is shared across threads and you want high throughput. Choose a plain HashMap when the map is confined to one thread or guarded externally. Note its weakly-consistent iterators never throw ConcurrentModificationException, unlike HashMap's fail-fast iterators.
go deeper
Knows HashMap is not thread-safe and ConcurrentHashMap is, and reaches for CHM when a map is shared between threads.
Contrasts CHM with synchronizedMap on the scalability axis (one global lock vs lock-free reads + per-bin write locks) and knows iterators are weakly consistent.
Explains the lock-free read / fine-grained write model, names CAS-on-empty-bin and per-bin locking, and identifies that single-op atomicity does not extend to check-then-act sequences.
Reasons about when CHM is the wrong tool (need for whole-map snapshots/cross-key invariants), memory/throughput trade-offs vs alternatives, and migration implications of the Java 7→8 internal change.
## What problem this solves A **map** (a.k.a. dictionary/associative array) stores **key → value** pairs and lets you look a value up by its key. Java's standard map is `HashMap`. A `HashMap` is **not thread-safe**: if two threads modify it at the same time without coordination, you can get **lost updates** (one write overwrites another), see a half-built internal state, or—in pre-Java-8 HashMaps—an actual **infinite loop** during a concurrent resize. *Thread-safe* means the data structure stays correct even when multiple threads use it at the same time. ## The three options 1. **`HashMap`** — fast, but only safe if a single thread touches it, or if you guard every access yourself. 2. **`Collections.synchronizedMap(map)`** — wraps any map so every method (`get`, `put`, …) is `synchronized` on one lock object. *Synchronized* means only one thread at a time may hold the lock; others wait. Because there is **one** lock for the whole map, even two threads reading different keys block each other. This is correct but a **scalability bottleneck**: throughput does not improve as you add threads/cores. 3. **`ConcurrentHashMap` (CHM)** — purpose-built for concurrency. Its key properties: - **Lock-free reads:** `get` traverses the table using `volatile`/memory-fenced reads and **acquires no lock**. A `volatile` field guarantees that a write by one thread becomes visible to readers. So many threads can read fully in parallel, even while another thread writes. - **Fine-grained write locking:** the table is an array of **bins** (a.k.a. buckets — a slot that holds the entries whose keys hash there). A write locks **only the one bin** it touches (using the bin's head node as the monitor), so writes to *different* bins proceed in parallel. The first insertion into an empty bin is done with a single **CAS** (compare-and-set: an atomic CPU instruction that sets a slot only if it still holds the expected value) and needs no lock at all. - **Weakly consistent iteration:** an iterator reflects the map's state at some point and may or may not show concurrent updates, but it **never throws `ConcurrentModificationException`** and never breaks. (`HashMap`'s iterator is *fail-fast*: it throws if the map changes during iteration.) ## When to pick which - Map shared by several threads, want throughput → **ConcurrentHashMap**. - Map used by one thread only, or already fully guarded by your own lock → **HashMap** (less overhead). - You need a *consistent snapshot* across the whole map (e.g. an atomic multi-key invariant) → CHM's per-operation atomicity is not enough; you need external coordination or a different structure. CHM guarantees atomicity of *single* operations, not of arbitrary multi-step sequences. ## Historical note The scalability mechanism changed across versions. Java 5–7 split the map into a fixed number of **segments**, each its own lock ("lock striping"); concurrency was capped by the segment count. Java 8 replaced that with **per-bin CAS + per-bin locking**, giving finer granularity and lower memory overhead. You should describe the Java 8+ model unless asked about history.
- Does iterating a ConcurrentHashMap throw ConcurrentModificationException if another thread writes during iteration?No. Its iterators are weakly consistent: they reflect the map at some point, tolerate concurrent modification, and never throw CME. HashMap's fail-fast iterator would throw.
- Is `if (!map.containsKey(k)) map.put(k, v);` thread-safe on a ConcurrentHashMap?No. Each call is atomic, but the gap between them is a race; two threads can both see the key absent. Use the atomic putIfAbsent or computeIfAbsent instead.
saying these in an interview costs you the question
- Saying ConcurrentHashMap locks the whole map on every operation
- Claiming reads take a lock
- Thinking synchronizedMap and ConcurrentHashMap scale the same
- Assuming a sequence of CHM calls is automatically atomic as a group