Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Concurrent Collections

Thread-safe collections for concurrent applications.

Understanding the difference is crucial for performance!

Diagram

ConcurrentHashMap provides thread-safe hash map operations with high concurrency.

Java 7: Segment locking (16 segments by default) Java 8+: CAS operations + synchronized blocks for individual buckets

Diagram
ConcurrentHashMapCache.java
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapCache<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
public V get(K key) {
return cache.get(key); // Thread-safe read
}
public void put(K key, V value) {
cache.put(key, value); // Thread-safe write
}
// Atomic operations
public V putIfAbsent(K key, V value) {
return cache.putIfAbsent(key, value); // Atomic!
}
public boolean remove(K key, V value) {
return cache.remove(key, value); // Atomic!
}
public V computeIfAbsent(K key, java.util.function.Function<K, V> mappingFunction) {
return cache.computeIfAbsent(key, mappingFunction); // Atomic!
}
}

CopyOnWriteArrayList creates a new copy on each write, making reads lock-free.

Diagram
CopyOnWriteExample.java
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteExample {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
// Multiple readers (no locking needed!)
for (int i = 0; i < 10; i++) {
final int readerId = i;
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
list.size(); // Lock-free read
}
System.out.println("Reader " + readerId + " finished");
}).start();
}
// Occasional writer
new Thread(() -> {
for (int i = 0; i < 10; i++) {
list.add("Item " + i); // Creates copy
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
}
}

We covered these in Producer-Consumer, but here’s a quick reference:

Queue TypeCharacteristicsUse Case
ArrayBlockingQueueArray-backed, boundedFixed-size queues
LinkedBlockingQueueNode-based, optionally boundedBetter throughput
PriorityBlockingQueuePriority orderingPriority-based processing
DelayQueueTime-based schedulingScheduled tasks
SynchronousQueueZero capacityDirect handoff

Python’s built-in types have limited thread-safety due to the GIL.

Diagram
thread_safety_example.py
import threading
# ❌ NOT thread-safe: Compound operation
counter = 0
def unsafe_increment():
global counter
counter += 1 # NOT atomic: read-modify-write
threads = [threading.Thread(target=unsafe_increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Unsafe result: {counter}") # May not be 10!
# ✅ Thread-safe: Single atomic operation
d = {}
def safe_operation():
d['key'] = 'value' # Atomic operation
threads = [threading.Thread(target=safe_operation) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Safe result: {len(d)}") # Always 1
# ✅ Thread-safe: Using locks
counter_safe = 0
lock = threading.Lock()
def safe_increment():
global counter_safe
with lock:
counter_safe += 1
threads = [threading.Thread(target=safe_increment) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Safe with lock: {counter_safe}") # Always 10

Python’s queue module provides thread-safe queue implementations.

queue_module.py
import queue
import threading
# FIFO Queue
fifo_queue = queue.Queue(maxsize=10)
# LIFO Queue (Stack)
lifo_queue = queue.LifoQueue(maxsize=10)
# Priority Queue
priority_queue = queue.PriorityQueue(maxsize=10)
def producer(q):
for i in range(5):
q.put(i)
print(f"Produced: {i}")
def consumer(q):
while True:
try:
item = q.get(timeout=1)
print(f"Consumed: {item}")
q.task_done()
except queue.Empty:
break
# Thread-safe operations
threading.Thread(target=producer, args=(fifo_queue,)).start()
threading.Thread(target=consumer, args=(fifo_queue,)).start()

For shared state across processes (not threads):

multiprocessing_manager.py
import multiprocessing
def worker(shared_dict, shared_list):
shared_dict['count'] = shared_dict.get('count', 0) + 1
shared_list.append(shared_dict['count'])
if __name__ == '__main__':
manager = multiprocessing.Manager()
shared_dict = manager.dict()
shared_list = manager.list()
processes = []
for _ in range(5):
p = multiprocessing.Process(target=worker, args=(shared_dict, shared_list))
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"Dict: {shared_dict}")
print(f"List: {shared_list}")

Collection TypeJavaPythonThread-Safety
HashMap/DictConcurrentHashMapdict + locksJava: Full, Python: Limited
ListCopyOnWriteArrayListlist + locksJava: Full, Python: Limited
QueueBlockingQueue variantsqueue.QueueBoth: Full
SetConcurrentSkipListSetset + locksJava: Full, Python: Limited

Design a thread-safe cache using concurrent collections.

Solution
import java.util.concurrent.ConcurrentHashMap;
public class ThreadSafeCache<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
public V get(K key) {
return cache.get(key);
}
public void put(K key, V value) {
cache.put(key, value);
}
public V computeIfAbsent(K key, java.util.function.Function<K, V> mappingFunction) {
return cache.computeIfAbsent(key, mappingFunction);
}
}

Q1: “What’s the difference between ConcurrentHashMap and synchronized HashMap?”

Section titled “Q1: “What’s the difference between ConcurrentHashMap and synchronized HashMap?””

Answer:

  • synchronized HashMap: Locks entire map for any operation (low concurrency)
  • ConcurrentHashMap: Fine-grained locking or CAS (high concurrency)
  • Performance: ConcurrentHashMap is much faster for concurrent access
  • Use ConcurrentHashMap: When you need thread-safe map with high concurrency

Q2: “When would you use CopyOnWriteArrayList?”

Section titled “Q2: “When would you use CopyOnWriteArrayList?””

Answer:

  • Use when: Reads vastly outnumber writes (e.g., 100:1 ratio)
  • Perfect for: Event listeners, configuration, read-heavy scenarios
  • Don’t use when: Frequent writes (too expensive - creates copy each time)
  • Trade-off: Expensive writes for lock-free reads

Q3: “Are Python’s built-in dict and list thread-safe?”

Section titled “Q3: “Are Python’s built-in dict and list thread-safe?””

Answer:

  • Single operations: Yes, atomic (e.g., dict[key] = value, list.append(item))
  • Compound operations: No, NOT thread-safe (e.g., if key in dict: dict[key] = value)
  • Solution: Use locks for compound operations or thread-safe collections
  • GIL: Provides some protection but doesn’t guarantee thread-safety for compound ops

Q4: “What’s the difference between ArrayBlockingQueue and LinkedBlockingQueue?”

Section titled “Q4: “What’s the difference between ArrayBlockingQueue and LinkedBlockingQueue?””

Answer:

  • ArrayBlockingQueue: Array-backed, always bounded, fixed memory, slightly lower throughput
  • LinkedBlockingQueue: Node-based, optionally bounded, dynamic memory, typically higher throughput
  • Choose: ArrayBlockingQueue for fixed-size needs, LinkedBlockingQueue for better performance


Continue learning concurrency:

Mastering concurrent collections is essential for building thread-safe systems! 📦