10

I was reading about ConcurrentModificationException and how to avoid it. Found an article. The first listing in that article had code similar to the following, which would apparently cause the exception:

List<String> myList = new ArrayList<String>();
myList.add("January");
myList.add("February");
myList.add("March");

Iterator<String> it = myList.iterator();
while(it.hasNext())
{
    String item = it.next();
    if("February".equals(item))
    {
        myList.remove(item);
    }
}

for (String item : myList)
{
    System.out.println(item);
}

Then it went on to explain how to solve the problem with various suggestions.

When I tried to reproduce it, I didn't get the exception! Why am I not getting the exception?

Raedwald
  • 46,613
  • 43
  • 151
  • 237

4 Answers4

8

According to the Java API docs Iterator.hasNext does not throw a ConcurrentModificationException.

After checking "January" and "February" you remove one element from the list. Calling it.hasNext() does not throw a ConcurrentModificationException but returns false. Thus your code exits cleanly. The last String however is never checked. If you add "April" to the list you get the Exception as expected.

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
        public static void main(String args[]) {

                List<String> myList = new ArrayList<String>();
                myList.add("January");
                myList.add("February");
                myList.add("March");
                myList.add("April");

                Iterator<String> it = myList.iterator();
                while(it.hasNext())
                {
                    String item = it.next();
                    System.out.println("Checking: " + item);
                    if("February".equals(item))
                    {
                        myList.remove(item);
                    }
                }

                for (String item : myList)
                {
                    System.out.println(item);
                }

        }
}

http://ideone.com/VKhHWN

bikeshedder
  • 7,337
  • 1
  • 23
  • 29
  • You've explained what's happening just fine, but not **why**. And the why is: Its a bug in `ArrayList`'s iterator class. – T.J. Crowder Feb 03 '13 at 15:09
  • Doesn´t answer the question. But not a bug as TJ says either. – zedoo Feb 03 '13 at 15:10
  • I was confused at first but after adding the print statement inside the while-loop it became clear. The API docs of [`Iterator.hasNext`](http://docs.oracle.com/javase/6/docs/api/java/util/Iterator.html#hasNext()) do not state that an `ConcurrentModificationException` is thrown so it really works as designed. It is a bit counter intuitive and to be true, I would expect `hasNext` to throw in those cases. This check was probably left out for performance reasons. – bikeshedder Feb 03 '13 at 15:10
  • I just added a reference to the API docs in my answer. It works as designed. `hasNext` does not throw but also does not return `true` in this case either. So it explains the behaviour and answers the question. – bikeshedder Feb 03 '13 at 15:14
  • 2
    *"According to the Java API docs Iterator.hasNext does not throw a `ConcurrentModificationException`."* **Facepalm** +1, deleted my answer. That is SERIOUSLY wrong, but clearly documented. :-) – T.J. Crowder Feb 03 '13 at 15:25
  • 2
    Actually, even `Iterator.next()` does not declare to throw CME. Only the JavaDoc for the whole `ArrayList` class says: _if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException_, but no concrete method is specified. – Natix Feb 03 '13 at 16:02
  • Why ```if("March".equals(item)) myList.remove(item);``` still throws exception? – Marcos Vasconcelos Jul 23 '19 at 16:35
5

From ArrayList source (JDK 1.7):

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

Every modifying operation on an ArrayList increments the modCount field (the number of times the list has been modified since creation).

When an iterator is created, it stores the current value of modCount into expectedModCount. The logic is:

  • if the list isn't modified at all during the iteration, modCount == expectedModCount
  • if the list is modified by the iterator's own remove() method, modCount is incremented, but expectedModCount gets incremented as well, thus modCount == expectedModCount still holds
  • if some other method (or even some other iterator instance) modifies the list, modCount gets incremented, therefore modCount != expectedModCount, which results in ConcurrentModificationException

However, as you can see from the source, the check isn't performed in hasNext() method, only in next(). The hasNext() method also only compares the current index with the list size. When you removed the second-to-last element from the list ("February"), this resulted that the following call of hasNext() simply returned false and terminated the iteration before the CME could have been thrown.

However, if you removed any element other than second-to-last, the exception would have been thrown.

Natix
  • 14,017
  • 7
  • 54
  • 69
2

I think the correct explanation is this extract from the javadocs of ConcurrentModificationExcetion:

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

So, if the iterator is fail fast it may throw the exception but there´s no guarantee. Try replacing February with January in your example and the exception is thrown (At least in my environment)

zedoo
  • 10,562
  • 12
  • 44
  • 55
  • While that paragraph is there, note the caveats in it. Meanwhile, though, bikeshedder [gets to the crux of the matter](http://stackoverflow.com/a/14673738/157247): `hasNext` doesn't throw `ConcurrentModificationException`! In the face of that simple fact, all my analysis in my answer (and yours in ours) is by-the-bye. – T.J. Crowder Feb 03 '13 at 15:23
0

The iterator checks that it has iterated as many times as you have elements left to see it has reached the end before it checks for a concurrent modification. This means if you remove only the second last element you don't see a CME in the same iterator.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130