Why Concurrent Collections in Java — HashMap, synchronizedMap, and the Real Solutions
Fast recall
Quick revision
Core answer
HashMap and ArrayList are not thread-safe — concurrent reads with writes corrupt the internal structure (infinite loops in pre-Java 8 HashMap resize, lost updates, ConcurrentModificationException). `Collections.synchronizedMap` / `synchronizedList` add a single big lock around every method — thread-safe but terrible throughput (all operations serialize on one lock) and still broken for iteration (you must synchronize externally, and iteration still throws ConcurrentModificationException). `java.util.concurrent` collections solve both: `ConcurrentHashMap` uses fine-grained per-bucket locking plus lock-free reads, scaling to hundreds of cores; `CopyOnWriteArrayList` for read-heavy listener lists; `BlockingQueue` family for producer-consumer; `ConcurrentSkipListMap` for sorted concurrent maps. Modern code should reach for java.util.concurrent collections by default — never Collections.synchronizedXxx in new code.Whether the candidate knows concrete failure modes of HashMap under concurrency, why `synchronizedMap` is not a real solution, and which `j.u.c` collection fits which workload
Thinking `Collections.synchronizedMap` is equivalent to `ConcurrentHashMap`. Not knowing that iteration still needs external synchronization on synchronizedMap. Missing the fail-fast vs fail-safe iterator distinction
Mention the pre-Java 8 `HashMap` infinite-loop bug during concurrent resize, that `ConcurrentHashMap`'s `computeIfAbsent` is atomic, the CopyOnWrite family's write cost being O(n), and LongAdder-style striping in modern ConcurrentHashMap
Interview-ready guide
Interview answer
Standard HashMap and ArrayList are not thread-safe, and the naive 'fix' — Collections.synchronizedMap — solves only part of the problem.
The problems with plain collections
- Structural corruption: pre-Java 8
HashMapfamously produced infinite loops during concurrent resize — threads spinning at 100% CPU forever - Lost updates:
map.put(k, map.get(k) + 1)races - `ConcurrentModificationException`: fail-fast iterators throw on any modification during iteration
Why `Collections.synchronizedMap` is not the answer
- Single big lock around every method — terrible throughput on multi-core
- Iteration still requires external `synchronized(map)` block — huge footgun
- Compound operations like
containsKey + putare still race conditions - Inherits
HashMapquirks like null handling
`ConcurrentHashMap` — the right default
- Per-bucket locking + lock-free reads — scales to hundreds of cores
- Atomic compound operations:
computeIfAbsent,merge,compute,replace - Fail-safe iterator — doesn't throw on concurrent modification, sees a weakly-consistent view
- No null keys or values — design decision to avoid ambiguity
- Use
ConcurrentHashMap.newKeySet()for concurrent sets
`CopyOnWriteArrayList` — right for ONE case
- Every write copies the whole internal array
- Reads are lock-free; iterators see a snapshot
- Perfect for read-heavy, rarely-written lists: event listeners, config lists
- Terrible for frequently-modified lists — write cost is O(n)
`BlockingQueue` family for producer-consumer
ArrayBlockingQueue— bounded, array-backedLinkedBlockingQueue— optionally bounded, separate put/take locks — best throughputPriorityBlockingQueue— priority heapDelayQueue— delayed elements (used byScheduledThreadPoolExecutor)SynchronousQueue— zero capacity, direct handoff (used byCachedThreadPool)LinkedTransferQueue— modern lock-free transfer queue- All have blocking
put/takethat correctly handle lock release during waits
Modern defaults I'd use in new code
- Map →
ConcurrentHashMap - Set →
ConcurrentHashMap.newKeySet() - Sorted map →
ConcurrentSkipListMap - Listener list →
CopyOnWriteArrayList - Work queue →
LinkedBlockingQueue(capacity)orArrayBlockingQueue - Unbounded non-blocking FIFO →
ConcurrentLinkedQueue
Compound operations still need atomic methods
map.computeIfAbsent(key, k -> load(k))instead ofcontainsKey + putmap.merge(key, 1L, Long::sum)instead ofget + putmap.replace(key, oldValue, newValue)for optimistic updates
Recommendation: In any code written after 2005, reach for java.util.concurrent collections first. ConcurrentHashMap should be the default map for any shared state — never HashMap behind a lock, never Collections.synchronizedMap. For producer-consumer pipelines, use the appropriate BlockingQueue. Reserve CopyOnWriteArrayList exclusively for read-heavy listener lists. And whenever you'd write containsKey followed by put, stop and use computeIfAbsent or merge instead — the atomic compound methods are what make ConcurrentHashMap truly thread-safe at the logical level, not just the memory level.
Why Concurrent Collections in Java — HashMap, synchronizedMap, and the Real Solutions
Direct Answer
Standard collections like HashMap and ArrayList are not thread-safe — concurrent reads with writes corrupt the internal structure (infinite loops in pre-Java 8 HashMap resize, lost updates, ConcurrentModificationException). Collections.synchronizedMap / synchronizedList add a single big lock around every method — thread-safe but terrible throughput (all operations serialize on one lock) and still broken for iteration (you must synchronize externally, and iteration still throws ConcurrentModificationException). java.util.concurrent collections solve both: ConcurrentHashMap uses fine-grained per-bucket locking plus lock-free reads, scaling to hundreds of cores; CopyOnWriteArrayList for read-heavy listener lists; BlockingQueue family for producer-consumer; ConcurrentSkipListMap for sorted concurrent maps. Modern code should reach for java.util.concurrent collections by default — never Collections.synchronizedXxx in new code.
Common Mistakes & Pitfalls
Thinking Collections.synchronizedMap is equivalent to ConcurrentHashMap. Not knowing that iteration still needs external synchronization on synchronizedMap. Missing the fail-fast vs fail-safe iterator distinction
Check your understanding
Answer these without looking back at the article.
- How did the pre-Java 8 `HashMap` concurrent-resize bug produce infinite loops?
- Why doesn't `ConcurrentHashMap` allow null keys or values?
- When would `CopyOnWriteArrayList` destroy your performance?
- What's the difference between a fail-fast and a weakly-consistent iterator?
- When is `SynchronousQueue` the right blocking queue to use?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.