HashMap Internals & Locks Interview Questions
KOTLIN › Concurrency
Walk me through what you'd actually observe, symptom-wise in production, when two threads trigger a HashMap resize at the same time. Not the mechanism, the actual bug report you'd get.
What a strong answer covers: The classic real-world symptom is a thread pegged at 100 percent CPU forever, historically caused by a circular linked list forming inside a bucket during concurrent resize on pre-Java-8 HashMap, distinguishable from a deadlock because the thread is spinning, not blocked, which a thread dump makes obvious. More generally you can also see silently lost or duplicated entries rather than a hang; either way the practical tell is that this shows up under load, not in a unit test, which is why it's such a nasty class of bug to catch before production.
You need a cache that's read far more often than it's written, shared across many threads. Walk me through how you'd choose between ConcurrentHashMap, a synchronized HashMap, and a ReentrantReadWriteLock-guarded HashMap.
What a strong answer covers: ConcurrentHashMap is the default choice for a generic concurrent map since its lock striping and CAS-based operations give near-lock-free reads and fine-grained writes, comfortably beating a synchronized HashMap which serializes even read-only access and kills throughput under read-heavy load. ReentrantReadWriteLock is worth the extra complexity mainly when you need atomicity across a compound operation spanning multiple keys or fields that ConcurrentHashMap's per-key atomic methods can't express on their own, otherwise it's added complexity for little real gain over ConcurrentHashMap.
Your cache class exposes get(key) and put(key, value), each individually synchronized on the same lock, but production shows a stale-then-overwritten value when two threads run get-compute-if-null-put logic concurrently. What's actually wrong, and how do you fix it without giving up concurrency?
What a strong answer covers: Each low-level call is individually atomic, but the read-compute-write sequence composed across two separate synchronized method calls is not atomic as a whole, so another thread can slip in between the get and the put and its write gets clobbered. The real fix is to either hold one lock across the entire read-compute-write sequence, accepting reduced concurrency, or better, switch to ConcurrentHashMap.computeIfAbsent, which performs the whole get-or-create atomically per key without any app-level locking at all.