Fail-fast vs fail-safe iterators
What happens when a collection changes while you're iterating over it?
- Fail-fast iterators (ArrayList, HashMap, HashSet, LinkedList…) throw
ConcurrentModificationExceptionas soon as they notice the collection was structurally modified other than through the iterator itself. Each collection keeps a modification counter (modCount); the iterator compares it with the value it expected. - Fail-safe iterators (the Javadoc calls them snapshot or weakly consistent) never throw:
CopyOnWriteArrayListiterates over a snapshot of the array taken when iteration began, so it never sees later changes.ConcurrentHashMapandConcurrentLinkedQueueiterators are weakly consistent: they reflect some changes made during iteration, and never fail.
Fail-fast behaviour is a bug detector, and the most common trigger is a single thread removing items inside a for-each loop. The fix is iterator.remove() or removeIf.
Side by side
| Fail-fast | Fail-safe (snapshot / weakly consistent) | |
|---|---|---|
| Throws ConcurrentModificationException | Yes | Never |
| How it works | Checks modCount on each step | Iterates a snapshot, or tolerates concurrent changes |
| Sees changes made during iteration | No: it throws | Snapshot: never; weakly consistent: maybe |
| Examples | ArrayList, HashMap, HashSet, LinkedList | CopyOnWriteArrayList, ConcurrentHashMap, ConcurrentLinkedQueue |
| Thread-safe | No | Yes |
| Extra cost | None | Copy-on-write copies the whole array on every write |
| Purpose | Detect bugs early | Allow changes while others iterate |
Example
List<String> names = new ArrayList<>(List.of("Asha", "Ravi", "Meera"));
for (String n : names) {
if (n.startsWith("R")) names.remove(n); // ConcurrentModificationException on the next step
}
names.removeIf(n -> n.startsWith("R")); // the fix
List<String> listeners = new CopyOnWriteArrayList<>(List.of("email", "sms"));
for (String l : listeners) {
listeners.add("push"); // no exception: the loop sees the old snapshot
}
System.out.println(listeners); // [email, sms, push, push]Common mistake
Removing items inside a for-each loop over an ArrayList. It throws ConcurrentModificationException even with a single thread.
Under the hood
Fail-fast checking is "best effort": modCount isn't volatile, so with several threads the exception may never come; never use it as a thread-safety mechanism. "Fail-safe" is an interview term, not a Javadoc term; say "snapshot" for copy-on-write collections and "weakly consistent" for concurrent ones. Copy-on-write collections suit read-mostly data such as listener lists and configuration, not frequently updated lists.
Check yourself
You add to a CopyOnWriteArrayList inside a for-each loop over it. What happens?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of Multithreading: beginner to advanced, Java 8 and collections, practically.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.