Learning goals
By the end of this chapter you should be able to:
- Choose
ConcurrentHashMap,CopyOnWriteArrayList, and blocking queues for concurrent workloads - Contrast synchronized wrappers vs purpose-built concurrent structures
- Use
ConcurrentHashMapbulk operations:compute,merge,reduce - Understand weakly consistent iterators and why fail-fast differs
Not every synchronized wrapper scales
Wrapping HashMap:
Map<String, String> m = Collections.synchronizedMap(new HashMap<>());
Every get/put acquires the same monitor. Under high contention, threads queue on one lock — correct but slow.
ConcurrentHashMap allows concurrent reads and scoped writes — default for shared mutable maps.
ConcurrentHashMap
ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
counts.merge("hits", 1, Integer::sum);
counts.computeIfAbsent("session", k -> new Session(k)); // atomic for key
Null policy: no null keys or values — NullPointerException.
Atomic compound ops on a single key — but check-then-act across keys still needs external coordination:
// Still racy if two keys must stay consistent together
if (map.containsKey(a) && map.containsKey(b)) { ... }
Iteration is weakly consistent — reflects state at some point, no ConcurrentModificationException, may or may not see concurrent updates.
CopyOnWriteArrayList / CopyOnWriteArraySet
On write, copy entire backing array. Reads never lock — great when reads vastly dominate.
List<Listener> listeners = new CopyOnWriteArrayList<>();
listeners.add(this::onEvent); // copies array
listeners.forEach(Listener::handle); // iterates snapshot
Poor choice for frequent writes — each add is O(n) copy.
Concurrent linked structures
ConcurrentLinkedQueue — lock-free MPMC queue, offer/poll.
LinkedBlockingQueue / ArrayBlockingQueue — blocking producer–consumer (see collections chapter).
PriorityBlockingQueue — thread-safe priority queue.
Pick blocking when threads must wait; concurrent linked when non-blocking retry or another layer handles back-pressure.
ConcurrentHashMap-backed Set
No ConcurrentHashSet in the JDK:
Set<String> unique = ConcurrentHashMap.newKeySet();
// or
Set<String> fromMap = Collections.newSetFromMap(new ConcurrentHashMap<>());
Same concurrency semantics as the backing map.
synchronized vs concurrent — summary
| Scenario | Prefer |
|---|---|
| Shared cache map | ConcurrentHashMap |
| Rare writes, many readers of listener list | CopyOnWriteArrayList |
| Producer–consumer buffer | LinkedBlockingQueue |
| Legacy single-lock simplicity | synchronizedMap / synchronizedList |
| Need sorted concurrent map | ConcurrentSkipListMap |
BlockingDeque and work stealing
LinkedTransferQueue, SynchronousQueue — specialized handoff patterns (executor internals).
ForkJoinPool uses work-stealing deques for divide-and-conquer — pairs with parallelStream and recursive tasks.
Know they exist; default business logic rarely constructs them directly.
Common mistakes
- Iterator remove on
CopyOnWriteArrayListduring for-each of same list — supported on iterator but understand snapshot semantics. - Using size() then put in non-atomic check — use
ConcurrentHashMapatomic methods. ConcurrentHashMapvalues collection iteration — views are weakly consistent; not a point-in-time snapshot of all values.- Blocking queue
addon full queue — throws; useofferorput. - Replacing all synchronized blocks with CHM — compound invariants spanning keys still need locks.
Practice checkpoint
-
Global dedupe set under concurrent adds — implementation? Answer:
ConcurrentHashMap.newKeySet()orCollections.newSetFromMap(new ConcurrentHashMap<>()). -
1000 threads read config list, once/day admin adds entry — list type? Answer:
CopyOnWriteArrayList. -
ConcurrentHashMapiterator throws CME on concurrent put? Answer: no — weakly consistent, no fail-fast CME. -
Rate-limit 5 concurrent DB calls — which j.u.c type? Answer:
Semaphore(5).
Interview angles
- CHM segmentation/CAS: finer than one global map lock; Java 8+ bin trees under heavy collisions.
- Copy-on-write trade-off: memory + copy cost vs read lock freedom.
- Blocking queue back-pressure:
putblocks producers — stabilizes memory under burst load. - When synchronized wrapper wins: low contention, legacy code, need
Collections.synchronizedMapsimplicity.