The gist of the matter is, as stated on both ArrayList
and ConcurrentModificationException
:
Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis.
Now a code sample from the Iterator
returned by ArrayList
:
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
return ;
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
As you can clearly see, in the case of ArrayList
the "best effort" is in checking for modifications when calling next()
and not when calling getNext()
. Your loop terminates without calling next()
a second time, hence no exception. Should you have 3 elements to begin with, or add an element it will fail. Also worth noting that if you modify the array list using reflection without updating the modCount
variable (naughty...), then the exception will not be thrown at all. modCount
is also not volatile, which again shows it's only best effort and has no guarantees, since the iterator might not see the latest value anyway.