Q14 of 40 · Core Java
What's the difference between HashMap and ConcurrentHashMap? How does ConcurrentHashMap achieve thread safety?
Short answer
Short answer: HashMap is not thread-safe — concurrent modifications can corrupt its internal structure or cause infinite loops during resize. ConcurrentHashMap allows safe concurrent reads and writes via per-bin synchronisation and CAS operations, without locking the entire map.
Detail
In single-threaded code, HashMap is faster and uses less memory. The moment you share it across threads without external synchronisation, behaviour becomes undefined: two simultaneous puts during a resize can corrupt the internal hash table, iterating while another thread inserts can throw ConcurrentModificationException, and reads can return stale or null values even when an entry was just inserted.
ConcurrentHashMap (Java 8+) uses a table of bins. Empty bins are written with CAS (compare-and-swap — a single atomic CPU instruction) requiring no lock. Non-empty bins are locked at the individual bin level using synchronized on the bin's head node, not on the whole map. This means two threads writing to different bins run truly concurrently. Reads never lock.
Important caveats:
size()andisEmpty()are approximate during concurrent modification — they return a best-effort count, not an exact snapshot.- Iterators are weakly consistent: they don't throw
ConcurrentModificationExceptionbut may or may not reflect insertions that happened after the iterator was created. - Compound operations like "check-then-act" still need atomic methods: use
compute(),merge(),computeIfAbsent()rather thanget()followed byput().
Prefer ConcurrentHashMap over Collections.synchronizedMap(new HashMap<>()) — the synchronised wrapper locks the entire map for every operation, defeating the purpose of fine-grained concurrency. Use ConcurrentHashMap when multiple threads write; use a plain read-only Map.copyOf() or Map.of() when the map is built once and then only read.
// EXAMPLE
ConcurrentMap.java
import java.util.concurrent.ConcurrentHashMap;
// ❌ Race condition — HashMap under concurrent writes
Map<String, Integer> counts = new HashMap<>();
// Two threads calling counts.put() simultaneously can corrupt the table
// ✅ Thread-safe with fine-grained locking
Map<String, Integer> counts = new ConcurrentHashMap<>();
// ❌ Still a race: get() then put() is not atomic
Integer current = counts.get("requests");
counts.put("requests", (current == null ? 0 : current) + 1);
// ✅ Atomic compute — read-modify-write in one operation
counts.compute("requests", (key, val) -> val == null ? 1 : val + 1);
// ✅ Even cleaner with merge
counts.merge("requests", 1, Integer::sum);
// computeIfAbsent — initialise a list per key thread-safely
Map<String, List<String>> grouped = new ConcurrentHashMap<>();
grouped.computeIfAbsent("smoke", k -> new java.util.concurrent.CopyOnWriteArrayList<>())
.add("TC_001");