ConcurrentHashMap (CHM) is the JDK's go-to thread-safe map. It allows concurrent reads with no locking and concurrent writes to different buckets — a dramatic improvement over Hashtable or Collections.synchronizedMap, which serialize every operation through a single monitor.
Two major design eras
Java 7 — segments. The map was an array of 16 (default) Segment objects, each a small HashMap guarded by its own ReentrantLock. Concurrency level was bounded by the number of segments: at most 16 writers in parallel.
Java 8+ — node-level locking. Segments are gone. The map is a single Node[] table. Writes use:
- CAS (
Unsafe.compareAndSwapObject) to install the first node in an empty bucket — lock-free in the common case. synchronizedon the bucket head when there's a collision — fine-grained, one lock per bucket.
A bucket holds a singly-linked list of Nodes; once it grows past TREEIFY_THRESHOLD (8) and the table is large enough (≥ 64 entries), it's converted to a red-black tree. Read operations (get) traverse the bucket without locking — they rely on volatile reads and final-field semantics.
Headline guarantees
- No null keys or values — at all, ever. Disambiguation in concurrent code is the reason.
- Atomic compound ops via
compute,computeIfAbsent,computeIfPresent,merge. - Weakly consistent iterators — never throw
ConcurrentModificationException. size()is best-effort — uses a striped counter, returnsint. UsemappingCount()(returnslong) for very large maps.
When CHM is the right tool
- Multi-threaded caches and rate limiters (
computeIfAbsentis gold). - Counters and accumulators (
merge, orLongAddervalues). - Any shared map where reads dominate but writes happen from many threads.
When it's not
- You need transactional multi-key updates → external lock or another structure.
- You need ordering →
ConcurrentSkipListMap. - You have a single writer → a plain
HashMapwithvolatilereference flips is often simpler.