Concurrent Collections
Standard collections (ArrayList, HashMap, etc.) are not thread-safe. If multiple threads read and write to them simultaneously, you get corrupted data or ConcurrentModificationException. Java provides two approaches to solve this.
Two Approaches
Section titled “Two Approaches”1. Synchronized Wrappers (old way)
Section titled “1. Synchronized Wrappers (old way)”List<String> syncList = Collections.synchronizedList(new ArrayList<>());Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());These wrap every method call in a synchronized block. Simple, but slow — only one thread can access the collection at a time, even for reads.
Critical: Iteration is still not safe — you must manually synchronize:
synchronized (syncList) { for (String item : syncList) { process(item); }}2. Concurrent Collections (modern way)
Section titled “2. Concurrent Collections (modern way)”Purpose-built for concurrency. Better performance because they use fine-grained locking, lock-free algorithms, or copy-on-write strategies instead of locking the entire collection.
ConcurrentHashMap
Section titled “ConcurrentHashMap”The go-to thread-safe Map. Uses segment-level locking (Java 7) / CAS + synchronized on individual buckets (Java 8+), so multiple threads can read and write simultaneously to different parts of the map.
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("Alice", 95);map.get("Alice");map.putIfAbsent("Bob", 87);map.remove("Alice");
// Atomic compound operations — these are why you use ConcurrentHashMapmap.compute("Alice", (key, val) -> (val == null) ? 1 : val + 1);map.merge("Alice", 1, Integer::sum);Key differences from HashMap:
- No null keys or values allowed
size()is an estimate under concurrency — usemappingCount()for a long-based count- Iterators are fail-safe (weakly consistent)
- Bulk operations:
forEach,search,reducewith parallelism threshold
// Parallel forEach — uses ForkJoinPool when entries exceed thresholdmap.forEach(2, (key, val) -> System.out.println(key + "=" + val));
// Search in parallel — stops at first matchString found = map.search(2, (key, val) -> val > 90 ? key : null);
// Reduce in parallelint total = map.reduce(2, (key, val) -> val, Integer::sum);CopyOnWriteArrayList
Section titled “CopyOnWriteArrayList”A thread-safe List optimized for read-heavy workloads. Every write (add, set, remove) creates a new copy of the internal array. Reads never block.
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a");list.add("b");list.get(0); // lock-free, very fast
// Iteration is always on a snapshot — safe even if another thread modifies the listfor (String item : list) { // never throws ConcurrentModificationException}Trade-offs:
- Reads: O(1), no locking — very fast
- Writes: O(n) — copies the entire array every time
- Best for: listener lists, configuration lists, small collections that are read far more than written
Avoid when writes are frequent or the list is large.
CopyOnWriteArraySet
Section titled “CopyOnWriteArraySet”Same concept as CopyOnWriteArrayList, but for Set. Backed by a CopyOnWriteArrayList internally, so contains() is O(n), not O(1).
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();set.add("listener1");set.add("listener2");Only use for small sets with rare modifications (e.g., event listeners).
BlockingQueue
Section titled “BlockingQueue”Designed for producer-consumer patterns. Threads block (wait) when trying to take from an empty queue or put into a full queue.
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10); // capacity 10
// Producer threadqueue.put("task"); // blocks if queue is full
// Consumer threadString task = queue.take(); // blocks if queue is emptyBlockingQueue Methods
Section titled “BlockingQueue Methods”| Throws | Returns special value | Blocks | Times out | |
|---|---|---|---|---|
| Insert | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| Remove | remove() | poll() | take() | poll(time, unit) |
| Examine | element() | peek() | — | — |
Implementations
Section titled “Implementations”| Implementation | Bounded? | Best For |
|---|---|---|
| ArrayBlockingQueue | Yes (fixed) | Bounded producer-consumer |
| LinkedBlockingQueue | Optional | High-throughput producer-consumer |
| PriorityBlockingQueue | No | Priority-based task processing |
| SynchronousQueue | Zero capacity | Direct handoff between threads |
| DelayQueue | No | Scheduled/delayed task execution |
// Bounded queue — producers wait when fullBlockingQueue<Task> taskQueue = new ArrayBlockingQueue<>(100);
// Unbounded queue — producers never block (watch for OOM)BlockingQueue<Task> taskQueue = new LinkedBlockingQueue<>();
// Priority queue — consumers get highest-priority itemBlockingQueue<Task> taskQueue = new PriorityBlockingQueue<>();ConcurrentLinkedQueue / ConcurrentLinkedDeque
Section titled “ConcurrentLinkedQueue / ConcurrentLinkedDeque”Lock-free, non-blocking concurrent queue/deque using CAS operations. Unlike BlockingQueue, these never block — they return null or false when empty/full.
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();queue.offer("item");String item = queue.poll(); // null if empty (never blocks)Use when you need a concurrent queue but don’t want blocking behavior.
ConcurrentSkipListMap / ConcurrentSkipListSet
Section titled “ConcurrentSkipListMap / ConcurrentSkipListSet”Thread-safe sorted collections. Like TreeMap/TreeSet but concurrent.
ConcurrentSkipListMap<String, Integer> sortedMap = new ConcurrentSkipListMap<>();sortedMap.put("banana", 2);sortedMap.put("apple", 1);// Iteration in sorted key order, thread-safeQuick Decision Guide
Section titled “Quick Decision Guide”| Need | Use |
|---|---|
| Thread-safe Map | ConcurrentHashMap |
| Thread-safe sorted Map | ConcurrentSkipListMap |
| Thread-safe List (read-heavy) | CopyOnWriteArrayList |
| Thread-safe List (write-heavy) | Collections.synchronizedList() or redesign |
| Producer-consumer queue | ArrayBlockingQueue or LinkedBlockingQueue |
| Non-blocking concurrent queue | ConcurrentLinkedQueue |
| Thread-safe Set | CopyOnWriteArraySet (small) or ConcurrentHashMap.newKeySet() |
Synchronized Wrappers vs Concurrent Collections
Section titled “Synchronized Wrappers vs Concurrent Collections”| Synchronized Wrappers | Concurrent Collections | |
|---|---|---|
| Locking | Entire collection | Fine-grained / lock-free |
| Read performance | Blocked by writers | Reads often lock-free |
| Iteration | Must sync manually | Fail-safe (snapshot) |
| Compound ops | Not atomic | Atomic (putIfAbsent, compute) |
| When to use | Quick fix, low contention | Production, high concurrency |
Bottom line: Always prefer java.util.concurrent collections over synchronized wrappers in new code.