Concurrent collections

Collections · lesson 15 of 42 · 4 min read

Share collections between threads without locks and without lost updates.

Open this lesson in the learning hub

Key points

  • Plain HashMap and ArrayList are not thread-safe. Concurrent writes lose updates or corrupt state.
  • ConcurrentHashMap locks only the bucket being written, so reads never block. merge is atomic per key.
  • It forbids null keys and values, because null could not tell "absent" from "mapped to null" in a racy read.
  • CopyOnWriteArrayList copies the whole array on every write. Perfect for listener lists: many reads, almost no writes.
  • A BlockingQueue hands work from producers to consumers. put and take block until they can proceed.
  • Their iterators are weakly consistent: they never throw ConcurrentModificationException, but may miss very recent changes.

Example

import java.util.*;
import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> safe = new ConcurrentHashMap<>();
        Map<String, Integer> unsafe = new HashMap<>();
        List<String> log = new CopyOnWriteArrayList<>();

        ExecutorService pool = Executors.newFixedThreadPool(4);
        for (int t = 0; t < 4; t++) {
            pool.submit(() -> {
                for (int i = 0; i < 5_000; i++) {
                    safe.merge("hits", 1, Integer::sum);
                    unsafe.merge("hits", 1, Integer::sum);
                }
                log.add(Thread.currentThread().getName());
            });
        }
        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);

        System.out.println("ConcurrentHashMap : " + safe.get("hits") + "   (always 20000)");
        System.out.println("plain HashMap     : " + unsafe.get("hits") + "   (usually wrong)");
        System.out.println("CopyOnWriteList   : " + log.size() + " entries, no exception");

        BlockingQueue<String> jobs = new LinkedBlockingQueue<>();
        jobs.put("build");
        jobs.put("test");
        System.out.println("take()            : " + jobs.take());
        System.out.println("poll(50ms)        : " + jobs.poll(50, TimeUnit.MILLISECONDS));
        System.out.println("poll(50ms) empty  : " + jobs.poll(50, TimeUnit.MILLISECONDS));
    }
}

The moment two threads touch a collection, switch to java.util.concurrent.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Collections course, and every lesson in it is listed on the Collections contents page.