Java Interview Questions

ConcurrentHashMap Internals: From Segment to CAS + synchronized

"How does ConcurrentHashMap achieve thread safety? What changed between JDK 1.7 and 1.8?"

Why Interviewers Ask This

ConcurrentHashMap is the canonical "concurrent data structure" interview question and a direct follow-up after HashMap. The JDK 1.7 → 1.8 evolution specifically tests whether you understand lock granularity and the tradeoffs between segment locks and fine-grained CAS.

How to Answer

  1. 1

    Start with the JDK 1.7 design: the map is split into 16 Segments (default), each Segment is a small HashMap protected by a ReentrantLock. Writes to different segments proceed in parallel; the fanout is bounded by the segment count.

  2. 2

    Describe JDK 1.8's shift: segments are abandoned in favor of per-bucket locking. The underlying structure becomes a Node array (like HashMap), and synchronized locks on the first node of each bucket. Empty bucket first insert uses CAS.

  3. 3

    Explain get is lock-free: Node's value and next are declared volatile, so reads traverse the bucket without locks. This gives extremely high read throughput.

  4. 4

    Cover put: (1) compute bucket index; (2) if bucket is empty, CAS a new Node as the head; (3) otherwise, synchronized on the head node, then insert into the bucket's list or tree.

  5. 5

    Walk through resize: the transfer happens in parallel — threads that encounter a resize in progress can be recruited to help migrate buckets. Migrated buckets are marked with a ForwardingNode sentinel; lookups find the ForwardingNode and follow a pointer to the new table.

Sample Answer

In JDK 1.7, ConcurrentHashMap used a Segment-based design. The map was divided into an array of Segments — default 16 — and each Segment was a small HashMap protected by its own ReentrantLock. A write only locked one segment, so writes to different segments ran in parallel. The concurrency limit was the segment count, and size() required locking every segment (later optimized with retries and optimistic sums). In JDK 1.8 the design was rewritten. Segments were removed; the map is a Node array like HashMap, with a linked list or red-black tree per bucket. Thread safety is achieved with a combination of CAS and synchronized: for the very first insert into an empty bucket, a CAS swaps in a new head Node — no lock needed. For subsequent inserts into a non-empty bucket, synchronized locks on the head Node of that specific bucket. Different buckets are independent, so concurrency is effectively per-bucket rather than per-segment — much finer granularity. get() is lock-free: Node's val and next fields are volatile, so reads just traverse the bucket and return. This makes reads extremely fast even under heavy write concurrency. Resize is also parallel — when a thread attempts a put during a resize, it can be recruited to help transfer buckets to the new table. Transferred buckets are replaced with a sentinel ForwardingNode that points to the new table, so concurrent readers see the ForwardingNode and transparently follow it to the new bucket. size() in 1.8 uses LongAdder-style striped counters — each thread updates its own counter cell to avoid contention, and size() sums the cells. This approach gives much higher throughput than 1.7, especially for workloads with concentrated writes. The tradeoff is the code is more complex and synchronized on the head Node creates some contention on "hot" buckets — but in practice it's dramatically better than segment locks.

Pro Tips

  • The 1.7 vs 1.8 contrast is what interviewers are really testing — answer both explicitly.

  • Mention CAS + synchronized specifically — the most common wrong answer is "uses ReentrantLock per bucket".

  • Cover ForwardingNode during resize — it signals a deep read of the source.

  • In a real interview, if you blank on "ForwardingNode" or "LongAdder", HireMe AI can surface them immediately.

Frequently Asked Questions

Why synchronized instead of ReentrantLock in 1.8?

Since JDK 6 synchronized has biased/lightweight/heavyweight lock upgrades and is often as fast as ReentrantLock for short critical sections, with less memory overhead per lock.

Does ConcurrentHashMap allow null keys or values?

No. Null ambiguity (does get(k)==null mean absent or null value?) is considered unsafe in concurrent contexts. HashMap allows it, ConcurrentHashMap rejects it.

Is size() accurate?

Under concurrent modification, size() is an estimate — it sums counter cells without locking, so the value is eventually consistent.

Worried About Going Blank in Your Interview?

HireMe AI listens to your interview in real-time, detects questions, and generates instant answer suggestions — so you never freeze up again.

Try HireMe AI Free