3

I tried to reproduce ConcurrentModificationException myself by writing the following code:

List<String> last = new ArrayList<>();
last.add("a");
last.add("b");
for(String i : last){
    System.out.println(i);
    last.remove(i);
}
System.out.println(last);

DEMO

Since, the documentation of ArrayList mentioned

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.

I expected that in single-threaded programs such detection is straghtforward. But the program printed

a
[b]

instead. Why?

St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • Possible duplicate of [Iterating through a list, avoiding ConcurrentModificationException when removing in loop](http://stackoverflow.com/questions/223918/iterating-through-a-list-avoiding-concurrentmodificationexception-when-removing) – Shiladittya Chakraborty Jan 06 '16 at 09:37

4 Answers4

6

Your code is equivalent to the following:

   List<String> last = new ArrayList<>();
   last.add("a");
   last.add("b");
   for(Iterator<String> i = last.iterator(); i.hasNext(); ) {
       String value = i.next();
       System.out.println(value);
       last.remove(value);
   }
   System.out.println(last);

The flow of the for loop is:

 System.out.println(value); // prints "a"
 last.remove(value);        // removes "a" from the list
 i.hasNext()                // exits the loop, since i.hasNext() is false
 System.out.println(last);  // prints "[b]" - the updated list

That's why you get your output and no ConcurrentModificationException. You will get ConcurrentModificationException if you'll add another value to your list (e.g. last.add("c")), because then i.hasNext() will be true after the first iteration and i.next() will throw the exception.

Grisha Weintraub
  • 7,803
  • 1
  • 25
  • 45
  • 2
    Yes, the crux of the matter is that i.hasNext() is false so the check for the co-modification never happens and the exception is never thrown. The reason it is false is because as far as the iterator is concerned, it has stepped once through a list of size one and so it is done – David Soroko Jan 06 '16 at 10:37
5

As explained in the documentation:

The iterators returned by this class's iterator and listIterator methods are fail-fast: 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

Because you are looping through the list using the new syntax for(String i : last) an iterator is created for you and you can't modify the list while looping it.

This exception is not related to multithreading. Also working with only one thread you can throw that exception.

Internally there is a variable modCount that is incremented for every modification to the structure of the list. When the iterator is first created it saves that value of modCount in a variable expectedModCount. Every subsequent modification check if that value of expectedModCount is equal to modCount. If not a ConcurrentModificationException is thrown.

I add the code of remove as an example. The same for add, addAll and all others methods that modify the list.

public E remove(int index) {
    rangeCheck(index);

    // Check if a modification should thrown a ConcurrentModificationException
    checkForComodification();  

    E result = parent.remove(parentOffset + index);
    this.modCount = parent.modCount;
    this.size--;
    return result;
}

final void checkForComodification() {
    if (expectedModCount != ArrayList.this.modCount)
        throw new ConcurrentModificationException();
    }
}
Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • 1
    You State "an iterator is created for you and you can't modify the list while looping it.", but that is exactly what St.Antario does. They are able to remove an element from the list while looping. They expected an exception to be thrown but it wasn't thrown. They'd like to know why. – DB5 Jan 06 '16 at 10:30
3

Itr class in ArrayList class has following methods

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

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

Here, modCount is the number of times list has been structurally modified. When we create for loop, internally an iterator will be created and expectedModCount will be initialized to modCount.

When there are only 2 elements in the list and after removing one element, for loop will check the condition using hasNext() method call. So, condition cursor != size (1!=1) will be met first. Hence, loop won't proceed further and ConcurrentModificationException will not be thrown.

But, when there are 1,3,4 etc number of elements are there in the list then, for loop will proceed further after hasNext() method call. But, while fetching the element using next() method inside for loop, it will call checkForComodification() and condition modCount != expectedModCount will be met. Hence, exception will be thrown.

Ravindra Devadiga
  • 692
  • 1
  • 6
  • 14
1

For two values, it will not fail because the for loop will be exited. Add a third element and you will get java.util.ConcurrentModificationException

List<String> last = new ArrayList<>();
last.add("a");
last.add("b");
last.add("c");
for(String i : last){
    System.out.println(i);
    last.remove(i);
}
System.out.println(last);