Memra

Concurrent collections & higher-level tools

ConcurrentHashMap vs synchronized wrappers, CountDownLatch coordination, and choosing the right tool.

Plain collections are not thread-safe

ArrayList, HashMap, and HashSet have no internal synchronization. Sharing a HashMap across threads without coordination can corrupt its internal structure or throw ConcurrentModificationException. You have three options, in increasing order of preference for high concurrency:

  1. Synchronize externally — wrap every access in synchronized. Correct but easy to get wrong, and serializes everything.
  2. Collections.synchronizedMap(map) — a wrapper that synchronizes each method on one lock. Every operation is mutually exclusive, so it is correct but a bottleneck; you must *still* hand-synchronize when iterating.
  3. ConcurrentHashMap — purpose-built for concurrency.

ConcurrentHashMap

ConcurrentHashMap allows many threads to read and update concurrently without locking the whole map, so throughput scales far better than a synchronized wrapper. Its iterators are weakly consistent — they never throw ConcurrentModificationException and reflect the map at some point during iteration rather than a frozen snapshot.

Crucially, its compound operations are atomic, which kills the check-then-act race from Lesson 2:

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
counts.putIfAbsent("a", 0);          // atomic: no race with another put
counts.merge("a", 1, Integer::sum); // atomic increment of the value

putIfAbsent, computeIfAbsent, and merge each run as one atomic step, so a multi-threaded word-count needs no external lock. Swapping new HashMap<>() for new ConcurrentHashMap<>() is the simplest fix when a shared map is the only contended state.

CountDownLatch: wait for N tasks

A CountDownLatch is a one-shot gate initialized to a count. Worker threads call countDown() as they finish; a coordinator calls await() to block until the count reaches zero:

import java.util.concurrent.CountDownLatch;

CountDownLatch done = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    pool.execute(() -> { doWork(); done.countDown(); });
}
done.await();   // unblocks once all 3 have counted down
System.out.println("all workers finished");

This is cleaner than calling join() on each thread when the workers run inside an executor (where you have Futures, not Threads). Other java.util.concurrent coordinators include CyclicBarrier (a reusable rendezvous point) and Semaphore (limit how many threads enter a region at once).

NORMAL ~/memra/learn/comp-308/concurrent-collections-tools utf-8 LF