Concurrency

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.

Java 21 (core language)

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