What is ConcurrentHashMap?
Unlike Collections.synchronizedMap(new HashMap<>()) (a single lock for the entire map), CHM allows many threads to work in parallel by locking only individual buckets.
π’ Junior Level
ConcurrentHashMap is a thread-safe Map for multi-threaded applications.
Unlike Collections.synchronizedMap(new HashMap<>()) (a single lock for the entire map), CHM allows many threads to work in parallel by locking only individual buckets.
Key difference from HashMap:
- HashMap: not thread-safe
- ConcurrentHashMap: thread-safe, no locks for reads
Map<String, Integer> map = new ConcurrentHashMap<>();
// Safe from multiple threads
map.put("A", 1);
map.get("A"); // β 1
// null is not allowed!
map.put("A", null); // β NullPointerException!
Rule of thumb:
- Single thread or thread-local β HashMap
- Multiple threads reading and writing β ConcurrentHashMap
- Multiple threads only reading β HashMap is fine (after safe publication)
π‘ Middle Level
Evolution
Java 7: Segment Locking
Java 7 β historical reference, not used in modern code.
Map divided into 16 segments
β Threads on different segments don't block each other
β size() = locks ALL segments!
Java 8+: Node-level Locking
CAS for empty buckets (no locks!)
Synchronized only for collisions (per bucket!)
β Maximum parallelism
Atomic operations
map.putIfAbsent(key, value); // Insert if absent
map.computeIfAbsent(key, k -> new ArrayList<>())
.add(value); // Compute and insert
map.merge(key, 1, Integer::sum); // Atomic update
Features
// β null is not allowed!
map.put(key, null); // β NullPointerException
// β οΈ size() is approximate!
int size = map.size(); // May change immediately!
// β
mappingCount() for large collections
long count = map.mappingCount(); // > 2^31
// β
Iterators do NOT throw Exception
// β Weakly consistent
// β Weakly consistent = iterator sees data at creation time and may (but is not required to) see subsequent changes. Never throws ConcurrentModificationException.
for (var entry : map) { ... } // Safe
π΄ Senior Level
Lock-free Reads
// get() WITHOUT locks!
// Through volatile fields:
// table array β volatile
// Node.val β volatile
// Node.next β volatile
// Happens-Before guarantee:
// β Reader sees the latest write
CAS + Synchronized
CAS (Compare-And-Swap) β an atomic CPU instruction: βwrite the new value only if the current value equals the expected one.β Works without locks β if two threads write simultaneously, one succeeds, the other retries.
put(key, value):
1. Bucket is empty β CAS (no locks!)
2. Bucket is occupied β synchronized on HEAD
β Only this bucket is locked
β Others work in parallel
Parallel Resizing
On resize:
β ForwardingNode in old buckets
β Writer threads SEE ForwardingNode
β Call helpTransfer() β HELP with resizing!
β Distributed resize, no full lock
CounterCells
Instead of a single volatile size:
β CounterCell array (like LongAdder)
β Each thread increments ITS OWN cell
β size() = sum of all cells
β Minimal contention!
Compute pitfall
// β Long-running operation in compute
map.computeIfAbsent(key, k -> {
Thread.sleep(10000); // Blocks the bucket for 10 seconds!
return value;
});
// β Recursive access β Deadlock!
map.computeIfAbsent(key1, k -> {
return map.get(key2); // May wait on the same bucket!
});
Java 21: Virtual Threads
synchronized in CHM may "pin" virtual threads
β But critical sections are VERY short
β Rarely a problem in practice
If it is a problem:
β async-profiler will show blocked time
Production Experience
Real scenario: Bad hashCode
// All keys β one bucket
// β Synchronized on a single bucket
// β 100 threads waiting β 0 parallelism!
// Solution: rewrite hashCode()
// Result: +500% throughput
Best Practices
- Default choice for concurrency
- null is not allowed β protects against ambiguity
- computeIfAbsent β atomic initialization
- No long-running operations in compute
- mappingCount() > size() for > 2^31 elements
- Bad hashCode β the main enemy of parallelism
- Weakly consistent iterators
Summary for Senior
- CAS for empty buckets, synchronized for collisions
- Lock-free reads through volatile
- Parallel resizing with helpTransfer()
- CounterCells = scalable counter
- compute β no long operations, no recursion
- null not allowed β a philosophical decision
- Bad hashCode β contention on a single bucket
π― Interview Cheat Sheet
Must know:
- ConcurrentHashMap β thread-safe Map, unlike HashMap it allows parallel work without a full map lock
- Java 8+: CAS for empty buckets (lock-free) + synchronized only on the bucket for collisions (instead of Segment locking from Java 7)
- Lock-free reads through volatile: table, Node.val, Node.next β get() without locks
- null not allowed (keys and values) β protects against ambiguity (null = key absent vs null = value)
- Iterators are weakly consistent β do not throw ConcurrentModificationException, may not see latest changes
- Parallel resizing: ForwardingNode + helpTransfer() β writer threads help with resizing
- CounterCells (like LongAdder) β each thread increments its own cell, size() = sum
- size() is approximate, mappingCount() for > 2^31 elements
Frequent follow-up questions:
- How does CHM differ from Collections.synchronizedMap? β synchronizedMap locks the ENTIRE map on every operation. CHM locks only one bucket (synchronized) or doesnβt lock at all (CAS).
- Why are null values not allowed in CHM? β To distinguish βkey not presentβ (get = null) from βvalue is nullβ. In HashMap this is impossible, creating ambiguity.
- What happens with a long-running operation in computeIfAbsent? β The bucket is locked for the entire computation. Other threads writing to the same bucket will wait. Recursive access β deadlock.
- How does CHM resize without a full lock? β It places ForwardingNode in old buckets. Writer threads, seeing it, call helpTransfer() and help transfer data.
Red flags (DO NOT say):
- β βCHM uses Segment lockingβ β thatβs Java 7, Java 8+ uses node-level locking
- β βCHM fully locks the map on writeβ β it locks only one bucket, others keep working
- β βCHM supports null valuesβ β no, NullPointerException for null keys and values
- β βsize() in CHM is exactβ β itβs approximate, may change immediately after the call
Related topics:
- [[19. How does ConcurrentHashMap ensure thread-safety]]
- [[14. What is Map and what implementations exist]]
- [[20. What is CopyOnWriteArrayList]]
- [[15. Difference between HashMap, LinkedHashMap, and TreeMap]]