Note:
ConcurrentModificationException
has nothing to do with threading! (Well, this is a bit too strong statement, as Rafael points point out in a comment: it might occur due to a threading race condition (but in such cases you probably have other problems as well)). A ConcurrentModificationException
may have nothing to do with threading!. Here I am talking about about the non threading related case.Here's a simple example to show the problem. We have a class that can register new listeners and when
fireChange
is called it calls changed
on the listeners. So the test listener here removes itself on the change call and boom, we get the ConcurrentModificationException:
public class IteratorTest {
final CollectionfListeners;
public IteratorTest(Collectionlisteners) {
fListeners = listeners;
}
static class Listener {
public void changed(IteratorTest subject) {
subject.removeListener(this);
}
}
public void addListener(Listener listener) {
fListeners.add(listener);
}
public void removeListener(Listener listener) {
fListeners.remove(listener);
}
public void fireChange() {
for (Listener listener : fListeners) {
listener.changed(this);
}
}
static void test(Collectioncoll) {
IteratorTest t = new IteratorTest(coll);
t.addListener(new Listener());
t.fireChange();
}
public static void main(String[] args) {
test(new ArrayList());
}
}
The problem can happen if you call out to "other code" (code someone else has written) and "other code" can change the collection while you are iterating. One solution is to iterate over a copy of the collection:
public void fireChange() {
Listener[] listeners=(Listener[]) fListeners.toArray();
for (Listener listener : listeners) {
listener.changed(this);
}
}
That helps. But there is a lot of code out there that iterates over a collection and calls "other code" and there is always a chance that the "other code" calls back to modify your collection and you get a ConcurrentModificationException....
The good news is: Unlike threading race conditions it happens deterministically. The bad news is: if you are the client it is often not easy to find a way out.
15 years ago, ET++ (the cool framework created by Erich Gamma and Andree Weinand in the 80ies) suffered form missing robust iterators. "Robust iterators" means robust to changes of the underlying collection. At that time, making a copy of a collection seemed an unacceptable overhead. So, Thomas Kofler added robust iterators to ET++ (the PDF has the pages on reverse order -- here is a readable version). The implementations are efficient and robust.
Robust iterators are so fundamental, I am really surprised that Java does not have them....
.