Understanding HashMap ConcurrentModificationException and Using ConcurrentHashMap in Java
This article explains why iterating and removing entries from a HashMap throws ConcurrentModificationException, describes the internal iterator mechanism, and provides correct solutions using an Iterator or replacing HashMap with ConcurrentHashMap, including detailed explanations of ConcurrentHashMap's structure and its get, put, remove, size, and iteration implementations.
HashMap is one of the most frequently used data structures in Java, but removing entries from a HashMap while iterating over it causes a ConcurrentModificationException because the iterator’s internal modCount does not match the map’s size after a modification.
The cause is that the iterator creates a KeyIterator which holds a snapshot of the map’s size; when the map is modified, the size changes while the iterator’s expected size remains unchanged, triggering the exception.
Two correct solutions are presented:
// Correct code using Iterator Iterator it = map.keySet().iterator(); while (it.hasNext()) { String s = it.next(); if (s.equals("s")) { it.remove(); } }
or replace HashMap with ConcurrentHashMap , which handles concurrent modifications internally.
ConcurrentHashMap
java.util.concurrent.ConcurrentHashMap provides a thread‑safe implementation of Map with higher concurrency by using lock‑striping: the map is divided into segments, each protected by its own lock, allowing multiple threads to modify different segments simultaneously.
Data Structure
ConcurrentHashMap consists of an array of Segment objects and, within each segment, an array of HashEntry objects. Each Segment acts as a re‑entrant lock, and each HashEntry stores a key‑value pair.
get Operation
public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
The get method first computes the hash, locates the appropriate segment, and then searches the bucket without acquiring a lock because reads are performed on volatile fields.
remove Operation
public V remove(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).remove(key, hash, null); }
The removal is performed while holding the segment’s lock; only operations on the same segment block each other.
put Operation
public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false); }
Insertion also acquires the segment lock, checks for existing keys, updates the value if present, or creates a new HashEntry otherwise, updating modCount and count accordingly.
size Operation
public int size() { // tries several times to get a consistent count without locking all segments // if counts differ, falls back to locking each segment and summing counts }
The method attempts to read the total element count by comparing modCount values across retries; if inconsistencies persist, it locks all segments to obtain an accurate size.
Iteration
Map map = new ConcurrentHashMap<>(); map.put("a", "a"); map.put("s", "s"); for (String s : map.keySet()) { if (s.equals("s")) { map.remove(s); } }
Iterating over a ConcurrentHashMap and removing entries does not throw ConcurrentModificationException because its iterators are weakly consistent: they reflect some, but not necessarily all, modifications made after the iterator was created.
Internally, the iterator traverses the segment array, then each segment’s table, and finally the linked list of entries, allowing safe traversal even when other threads modify the map.
Qunar Tech Salon
Qunar Tech Salon is a learning and exchange platform for Qunar engineers and industry peers. We share cutting-edge technology trends and topics, providing a free platform for mid-to-senior technical professionals to exchange and learn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.