12 · Concurrent Collections & BlockingQueues
Level: Intermediate
Pre-reading: 08 · Synchronization Mechanisms
ConcurrentHashMap
Problem with HashMap + synchronized
// ❌ Inefficient: entire map locked
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// All threads wait even if accessing different keys!
ConcurrentHashMap Solution
// ✅ Efficient: only segment locked
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
// Threading into 16 segments by default
// Multiple threads can write to different segments simultaneously
map.put("key1", "value1"); // Threads can write concurrently
map.get("key1"); // Reads are lock-free (mostly)
Operations
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Basic operations
map.put("count", 1);
map.get("count");
// Atomic compound operations
map.putIfAbsent("count", 0);
map.compute("count", (k, v) -> v == null ? 1 : v + 1);
map.computeIfAbsent("count", k -> 0);
// Iteration is thread-safe (weakly consistent)
for (String key : map.keySet()) {
System.out.println(key);
}
Weak Consistency
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);
// Iteration doesn't lock entire map
// May see some updates made during iteration, not others
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.equals("a")) {
map.put("c", 3); // OK: doesn't cause ConcurrentModificationException
}
}
CopyOnWriteArrayList
Use Case
Many readers, few writers.
// ❌ synchronized list: all reads are slow
List<String> list = Collections.synchronizedList(new ArrayList<>());
// ✅ CopyOnWriteArrayList: reads are fast
List<String> list2 = new CopyOnWriteArrayList<>();
// Reads use latest snapshot (no lock)
for (String item : list2) {
System.out.println(item); // Fast
}
// Writes copy entire array (expensive)
list2.add("new"); // Slow, but only one writer at a time
Behavior
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a");
list.add("b");
// Iteration on SNAPSHOT
Iterator<String> it = list.iterator();
// Doesn't interfere with iteration
list.add("c"); // Creates new copy, iteration continues on old snapshot
BlockingQueue
Use Case
Producer-consumer pattern.
import java.util.concurrent.*;
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10); // Bounded
// Producer
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
queue.put("item-" + i); // Blocks if full
} catch (InterruptedException e) {}
}
}).start();
// Consumer
new Thread(() -> {
while (true) {
try {
String item = queue.take(); // Blocks if empty
System.out.println("Consumed: " + item);
} catch (InterruptedException e) {}
}
}).start();
Queue Types
| Type | Behavior |
|---|---|
| LinkedBlockingQueue | Unbounded (or bounded) |
| ArrayBlockingQueue | Fixed-size array-backed |
| PriorityBlockingQueue | Elements ordered by priority |
| SynchronousQueue | No internal storage, direct hand-off |
Other Concurrent Collections
ConcurrentLinkedQueue
// Lock-free, unbounded queue
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("item1"); // Add
String item = queue.poll(); // Remove (or null if empty)
ConcurrentSkipListMap
// Lock-free, sorted map
ConcurrentSkipListMap<String, Integer> map = new ConcurrentSkipListMap<>();
map.put("b", 2);
map.put("a", 1);
// Iterate in sorted order
for (String key : map.keySet()) {
System.out.println(key); // a, b (sorted)
}
Choosing Collection
graph TD
A["What do you need?"] --> B["Map?"]
A --> C["List?"]
A --> D["Queue?"]
B -->|Many threads| E["ConcurrentHashMap"]
B -->|Few threads| F["synchronizedMap"]
C -->|Many readers| G["CopyOnWriteArrayList"]
C -->|Many writers| H["synchronizedList"]
D -->|Producer-Consumer| I["BlockingQueue"]
D -->|High throughput| J["ConcurrentLinkedQueue"]
style E fill:#e3f2fd
style G fill:#e3f2fd
style I fill:#e3f2fd
Key Takeaways
| Collection | Thread Safety | Use Case |
|---|---|---|
| ConcurrentHashMap | Partial locking | Many readers & writers |
| CopyOnWriteArrayList | Lock-free reads | Many readers, few writers |
| BlockingQueue | Full locking | Producer-consumer |
| ConcurrentLinkedQueue | Lock-free | High throughput queue |
📚 Read the Original Blog Post
For more details and examples, read:
- Concurrent Collections — Thread-safe data structures
When should I use synchronizedList instead of CopyOnWriteArrayList?
synchronizedList is better for balanced read/write. CopyOnWriteArrayList is better for heavy reads, light writes.
What happens if I iterate and modify a CopyOnWriteArrayList?
You won't get ConcurrentModificationException. Modifying creates a new copy; your iteration continues on the old snapshot.
Why would I choose SynchronousQueue?
For direct hand-off between producer and consumer threads with no buffering; good for thread pool work queues.