2

Note: Not a duplicate of this question: Why am I not getting a java.util.ConcurrentModificationException in this example?. The question is, why the exception is not being thrown.

If we use foreach on List<String> and try to remove any element from it then it throws java.util.ConcurrentModificationException but why following code is not throwing the same exception and also not processing 2nd object of User?

public class Common {

    public static void main(String[] args) {
        User user1 = new User();
        user1.setFirstname("Vicky");
        user1.setLastname("Thakor");

        User user2 = new User();
        user2.setFirstname("Chirag");
        user2.setLastname("Thakor");

        List<User> listUser = new ArrayList<User>();
        listUser.add(user1);
        listUser.add(user2);

        int count = 0;
        for (User user : listUser) {
            System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
            count++;
            listUser.remove(user);
        }
    }
}

The output is:

0:Vicky Thakor

Community
  • 1
  • 1
Vicky Thakor
  • 3,847
  • 7
  • 42
  • 67
  • ArrayList's iterator should be a fail fast iterator, perhaps you are just not seeing the exception. Also it may be better to change your example to code everyone can compile and run (not sure what `User` is). From ArrayList's 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.` – NESPowerGlove Dec 04 '14 at 21:45

4 Answers4

4

Although the question is not an exact duplicate, the linked question: Why am I not getting a java.util.ConcurrentModificationException in this example? contains the answer.

The check that verifies whether to throw the exception is made when the next() method of the iterator has called, but this happens only if hasNext() returns true. In your case, when the list has two elements and you remove the first one in the first iteration the condition:

public boolean hasNext() {
        return cursor != size(); // 1 != 1 after first iteration
}

is accidentally false, because cursor is at 1 and that's what size() is, at the moment. So next() is not called, and the exception is not being thrown.

Add a third element:

listUser.add(user2);

and the exception will be thrown. However, you should not rely on that behavior, because, as the documentation explains, it is not guaranteed:

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.

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
2

As NESPowerGlove stated in the comment sections the interator returns a fail-fast iterator according to the java doc, but it includes this

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. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

(Emphasis mine) So, there is no guarantee that the exception will be thrown in the case of a modification.

enrique7mc
  • 897
  • 9
  • 13
0

Your loop could be rewritten as

    Iterator<User> iterator = listUser.iterator();
    while (iterator.hasNext()) {
        User user = iterator.next();
        System.out.println(count + ":" + user.getFirstname() + " " + user.getLastname());
        listUser.remove(user);
    }

and if we unroll the loop, it will look like

    Iterator<User> iterator = listUser.iterator();
    System.out.println(iterator.hasNext()); // prints true, loop executes
    User user = iterator.next();
    System.out.println(count + ":" + user.getFirstname() + " " + user.getLastname());
    listUser.remove(user);

    System.out.println(iterator.hasNext()); // prints false, loop stops

Until the second iterator.hasNext() call, collection is not modified, so all works as expected. Now the question is why the second iterator.hasNext() call returns false instead of throwing ConcurrentModificationException? Let's check ArrayList sources. I'll quote JDK 8 sources.

ArrayList.java, ArrayList's iterator declared at line 840 in class Itr. And it's hasNext() method is quite simple:

    int cursor;       // index of next element to return
    ...

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

cursor is the index of the next element to return, size belongs to the outer ArrayList instance.

Checking for comodification implemented in the next() method.

Please note that you must not rely on this check in your own code. It designed to fight bugs, not provide you logic to rely upon. And it's not guaranteed to catch all your bugs (as demonstrated in your question). Probably performance is the reason why this check isn't implemented in hasNext() method.

vbezhenar
  • 11,148
  • 9
  • 49
  • 63
0

Try this:

int count = 0;
for (User user : listUser) {
   listUser.remove(user);
   System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
   count++;
}
for (User user : listUser) {
   listUser.remove(user);
   System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
   count++;
}

I created the User class in less than 10 seconds. Contains two strings. But for the question, the User class is irrelevant. Assume Object and the problem remains. The key is to run this on debug and see what happens.

You will see the ConcurrentModificationException if you add the same for-each loop and try to iterate over the same list. The second time around, it would display:

0:Vicky Thakor
1:Chirag Thakor

Then throw the exception. Many people fail to remember that ConcurrentModificationException is a RuntimeException, and therefore, not documented on the API on the remove() method. The issue here is that you should trust the documentation. The documented safe way to remove an element while iterating is using the Iterator.

hfontanez
  • 5,774
  • 2
  • 25
  • 37