⚡ Advanced Concurrency · Advanced

ConcurrentHashMap in Java

Thread-safe map, atomic compute/merge, no null keys or values.

🧩 The mysteryMany threads, one map. A plain HashMap can corrupt itself; wrapping it in one big lock makes everyone queue. ConcurrentHashMap does neither. How?

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

✗ get then put
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.

✓ merge
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);
🔮 Predict it

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"));
  1. 3 1
  2. 1 1
  3. 4 0
Show the answer

"x" appears three times, so merge stores 1, then 1+1, then 2+1 = 3. "y" appears once: 1.

🔮 Predict it

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));
  1. Throws NullPointerException
  2. null
  3. 1
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.

💼 In the real world

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

  1. merge(k, 1, Integer::sum) is an atomic per-key counter
  2. get() then put() across two calls is still a race
  3. No null keys or values — NullPointerException
  4. Iterators are weakly consistent: no ConcurrentModificationException
🤯 Did you know?

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"));
  1. 2 1
  2. 1 1
  3. 3 0
  4. 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);
  1. Throws NullPointerException
  2. {k=null}
  3. {}
  4. Compile error
Check your answer

Throws NullPointerException. ConcurrentHashMap rejects null keys and values at runtime.

Next: lists that copy themselves on every write, and queues that make producers wait: the rest of the concurrent collections toolbox.