How Java works

Pick a topic, write code and watch what happens inside, step by step.

Memory

Collections

Concurrency

Language and JVM

Topics

How ConcurrentHashMap works

Why is HashMap not enough?

HashMap is not thread-safe: threads writing at once can lose Nodes and break a resize.

Hashtable puts every method behind one lock: threads wait for each other even when they use different buckets.

ConcurrentHashMap narrows the lock to a bucket, so different buckets rarely get in each other's way. The Segment design of Java 7 was removed in Java 8.

Empty bucket: CAS

If a bucket is empty, no lock is needed. The thread uses CAS: if it is null, write the Node.

If two threads write to the same empty bucket, one wins. The CAS of the other fails and its put starts over, and now the bucket is full.

Full bucket: lock

If the bucket is full, the thread takes synchronized on its first Node. The lock covers this bucket only.

A thread writing to the same bucket waits, and a thread writing to another bucket does not. This is the key difference from Hashtable.

Why no null?

In HashMap, when get returns null you can call containsKey: is the key missing, or is the value null?

With many threads the map can change between those two calls, so the answer cannot be trusted. To avoid this, ConcurrentHashMap rejects null keys and null values.

Press put(null, 1) on the left.

Resize: a little earlier, and together

There are 11 keys here. Press put for k12 on the left: the table grows on the 12th key. HashMap grows on the 13th.

During the move, a ForwardingNode goes into each moved bucket. get follows it to the new table, and a thread doing put helps with the move.

Nodes that moved are marked on the right. Nodes sometimes end up reversed in a chain, which matches the JDK code.

Atomic operations and size

Each call is safe on its own, but get followed by put is not atomic: another thread can write in between.

That is what merge, compute, computeIfAbsent and putIfAbsent are for.

size() can be approximate: the counter is split into baseCount and CounterCells. While threads write, the result may not be exact.