ConcurrentHashMap and Atomic Classes

Thread-safe Collections

NeedUseInstead of
Shared mapConcurrentHashMapHashMap (unsafe), Hashtable/synchronizedMap (one big lock)
Read-mostly list (listeners)CopyOnWriteArrayListsynchronizedList
Producer–consumer queueArrayBlockingQueue, LinkedBlockingQueuewait/notify
Non-blocking queueConcurrentLinkedQueueLinkedList
Sorted concurrent mapConcurrentSkipListMapTreeMap

Note: ConcurrentHashMap locks only small parts (bins) and reads without locking. Use its atomic methods (merge, compute, computeIfAbsent, putIfAbsent) — a separate get then put is still a race.

Counting Page Views from Many Threads

Example

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

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> views = new ConcurrentHashMap<>();
        AtomicLong totalViews = new AtomicLong();
        LongAdder errors = new LongAdder();          // fastest for heavy-write counters
        String[] pages = {"/home", "/cart", "/home", "/product/1", "/home"};

        try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
            for (int t = 0; t < 4; t++) {
                pool.submit(() -> {
                    for (int i = 0; i < 1000; i++) {
                        String page = pages[i % pages.length];
                        views.merge(page, 1, Integer::sum);   // atomic read-modify-write
                        totalViews.incrementAndGet();
                        if (i % 100 == 0) errors.increment();
                    }
                });
            }
        }
        System.out.println(new TreeMap<>(views));
        System.out.println("Total views: " + totalViews.get());
        System.out.println("Errors: " + errors.sum());
    }
}

Output

{/cart=800, /home=2400, /product/1=800}
Total views: 4000
Errors: 40

Atomic Classes and CAS

AtomicInteger, AtomicLong, AtomicBoolean and AtomicReference use the CPU instruction compare-and-swap (CAS): "set to new value only if it is still the old value", retrying on conflict. No locks, no blocking.

Example

import java.util.concurrent.atomic.*;

public class Main {
    public static void main(String[] args) {
        AtomicInteger stock = new AtomicInteger(10);
        System.out.println(stock.incrementAndGet());           // 11
        System.out.println(stock.getAndAdd(5));                // returns 11, now 16
        System.out.println(stock.compareAndSet(16, 0));        // true: was 16, set to 0
        System.out.println(stock.compareAndSet(16, 99));       // false: value is 0
        System.out.println(stock.updateAndGet(x -> x + 7));    // 7
        System.out.println(stock.accumulateAndGet(3, Math::max)); // 7

        AtomicReference<String> status = new AtomicReference<>("PENDING");
        boolean shipped = status.compareAndSet("PENDING", "SHIPPED");
        System.out.println(shipped + " " + status.get());
    }
}

Output

11
11
true
false
7
7
true SHIPPED