ConcurrentHashMap in Java
Thread-safe map, atomic compute/merge, no null keys or values.
Built for crowds
ConcurrentHashMap is a thread-safe Map where reads don't lock and writes lock only a small part of the table, so threads rarely wait. Compare Collections.synchronizedMap(...): one lock around every call, so all threads queue in single file.
Two safe calls, one unsafe pair
Integer old = hits.get(page);
int next = old == null ? 1 : old + 1;
hits.put(page, next);Each call is thread-safe, but the pair is a check-then-act race: two threads read 4, both write 5. volatile or putIfAbsent won't fix it.
hits.merge(page, 1, Integer::sum);One call, atomic for that key: the whole read-modify-write happens without interference.
Atomic per-key updates
merge(k, 1, Integer::sum): if k is absent, store 1; otherwise combine the old value with 1 using Integer::sum. compute and computeIfAbsent are atomic per key too, perfect for counters and caches.
counts.merge(word, 1, Integer::sum);
cache.computeIfAbsent(id, this::load);
scores.compute(team,
(k, v) -> v == null ? 3 : v + 3);Your turn
What does this print?
Map<String, Integer> m =
new ConcurrentHashMap<>();
for (var w : List.of("x", "y", "x", "x"))
m.merge(w, 1, Integer::sum);
System.out.println(m.get("x"));
System.out.println(m.get("y"));3 11 14 0
Show the answer
"x" appears three times, so merge stores 1, then 1+1, then 2+1 = 3. "y" appears once: 1.
A null lookup
A HashMap would print null here. What does ConcurrentHashMap do?
Map<String, Integer> m =
new ConcurrentHashMap<>();
m.put("a", 1);
System.out.println(m.get(null));Throws NullPointerExceptionnull1
Show the answer
It throws NullPointerException: ConcurrentHashMap rejects null keys and null values, even in lookups, and put(k, null) throws too. Why? Under concurrency, a null from get() must unambiguously mean "absent", because you can't atomically ask containsKey and then get.
Weakly consistent iteration
Iterating while other threads modify the map never throws ConcurrentModificationException. The iterators are weakly consistent: they may or may not reflect updates made during the loop, but they never fail.
Interview classic
"ConcurrentHashMap vs synchronizedMap vs Hashtable?" Answer: the last two use one lock for everything; CHM lets many threads work at once, offers atomic merge/compute, and forbids nulls. In production it backs caches (computeIfAbsent), counters (merge) and registries.
Key takeaways
- merge(k, 1, Integer::sum) is an atomic per-key counter
- get() then put() across two calls is still a race
- No null keys or values — NullPointerException
- Iterators are weakly consistent: no ConcurrentModificationException
Before Java 8, ConcurrentHashMap was split into 16 "segments" by default, each with its own lock. Java 8 replaced them with per-bin locking plus CAS.
Practice questions
What does this print?
var m = new ConcurrentHashMap<String, Integer>();
for (String w : List.of("a", "b", "a")) {
m.merge(w, 1, Integer::sum);
}
System.out.println(m.get("a") + " " + m.get("b"));- 2 1
- 1 1
- 3 0
- 2 2
Check your answer
2 1. merge inserts 1 for a new key and otherwise combines the old value with 1 using Integer::sum. "a" appears twice, "b" once.
What does this print?
var m = new ConcurrentHashMap<String, String>();
m.put("k", null);
System.out.println(m);- Throws NullPointerException
- {k=null}
- {}
- Compile error
Check your answer
Throws NullPointerException. ConcurrentHashMap rejects null keys and values at runtime.