56. Concurrent Collections
java.util.concurrent provides thread-safe collections: ConcurrentHashMap (lock-striped map), CopyOnWriteArrayList (read-heavy lists), and BlockingQueue (producer-consumer channel) โ each with different performance profiles.
Explanation
Collections.synchronizedMap(map) wraps a HashMap with a single lock, serialising all access. ConcurrentHashMap uses lock striping (segments/buckets are locked independently), allowing concurrent reads and writes on different buckets โ far higher throughput under contention. It does not allow null keys or values (unlike HashMap).
CopyOnWriteArrayList creates a new copy of the underlying array on every write. Reads are lock-free (from the immutable snapshot). This is efficient only when reads vastly outnumber writes โ a good fit for listener/observer lists. Do not use it as a general-purpose list.
BlockingQueue implementations (ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue) block the producer when the queue is full and block the consumer when empty โ perfect for producer-consumer patterns without manual wait/notify.
Example
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ConcurrentCollections {
public static void main(String[] args) throws InterruptedException {
// ConcurrentHashMap: high-concurrency map
ConcurrentHashMap<String, AtomicInteger> wordCount = new ConcurrentHashMap<>();
String[] words = {"java", "java", "python", "java", "python", "ruby"};
for (String word : words) {
wordCount.computeIfAbsent(word, k -> new AtomicInteger()).incrementAndGet();
}
wordCount.forEach((word, count) -> System.out.println(word + ": " + count.get()));
// CopyOnWriteArrayList: safe for listener registration (few writes, many reads)
CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
listeners.add("listenerA");
listeners.add("listenerB");
// Can iterate safely even while another thread adds/removes
for (String listener : listeners) {
System.out.println("Notifying: " + listener);
}
// BlockingQueue: classic producer-consumer
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
Thread producer = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
queue.put("item-" + i); // blocks if full
System.out.println("Produced: item-" + i);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
Thread consumer = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
String item = queue.take(); // blocks if empty
System.out.println("Consumed: " + item);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
producer.start(); consumer.start();
producer.join(); consumer.join();
}
}Sources
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html โ ConcurrentHashMap API
- https://docs.oracle.com/javase/tutorial/essential/concurrency/collections.html โ Concurrent collections tutorial