Lesson 41.3 · Concurrency-Aware Data Structures
Concurrent Collections and Executors
ConcurrentHashMap gives thread-safe maps with atomic merge/compute. ExecutorService runs tasks on a pool of threads and returns Futures. Reach for these before writing locks yourself.
12 min
Think of it like this
A kitchen with several chefs (a thread pool) taking order tickets from one rail (the task queue), all writing tallies on a whiteboard designed so two chefs can write at once without smudging each other (a concurrent map).
1.The standard toolkit
ConcurrentHashMap: reads don't block, writes lock only a small part of the table, and merge, compute and putIfAbsent are atomic. Check-then-act sequences (if (!map.containsKey(k)) map.put(k, v)) are still races; use the atomic methods instead.
ExecutorService: Executors.newFixedThreadPool(n) reuses n threads; submit returns a Future whose get() waits for the result. Always shutdown() when done. Since Java 21, Executors.newVirtualThreadPerTaskExecutor() creates cheap virtual threads for I/O-heavy work.
Other useful classes: CopyOnWriteArrayList (rare writes, many reads), ConcurrentLinkedQueue (lock-free queue), LongAdder (high-contention counters), and ConcurrentSkipListMap (a sorted concurrent map).
import java.util.TreeMap;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int t = 0; t < 4; t++)
pool.submit(() -> {
for (int i = 0; i < 1000; i++) {
counts.merge("a", 1, Integer::sum); // atomic read-modify-write
counts.merge(i % 2 == 0 ? "even" : "odd", 1, Integer::sum);
}
});
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(new TreeMap<>(counts));
}
}Output
{a=4000, even=2000, odd=2000}Remember
- Use atomic map methods, not check-then-act.
- Pools reuse threads; shut them down.
- Virtual threads for blocking I/O (Java 21+).
Common mistakes
- Wrapping a HashMap in synchronized blocks everywhere instead of using ConcurrentHashMap.
- Forgetting shutdown(), which keeps the JVM alive.