Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abnormal behaviour of java.util.List based on number of elements in it [duplicate]

I know that if the Collection will be changed while some thread is traversing over it using iterator, the iterator.next() will throw a ConcurrentModificationException.

But it shows different behavior depending on the number of elements in the list.

I tried a code snippet in which I traversed a list in for-each loop and in between it's traversal removed an element from the list using remove() method of the list.

Ideally it should throw a ConcurrentModificationException in this condition without depending on number of elements in the list, But it is not true when number of elements in list are two.

Case 1: Number of elements in list - 1

 public static void main(String[] args) 
    {
        List<String> list=new ArrayList<String>();
        list.add("One");

        for (String string : list) 
        {
            System.out.println(string);
            list.remove(string);
        }
    }

Output: One

Exception in thread "main" java.util.ConcurrentModificationException

That's was as expected.

Case 2: Number of elements in list - 2

 public static void main(String[] args) 
    {
        List<String> list=new ArrayList<String>();
        list.add("One");
        list.add("two");

        for (String string : list) 
        {
            System.out.println(string);
            list.remove(string);
        }
    }

Output: One

No Exception is thrown?????????

Case 3: Number of elements in list - 3

 public static void main(String[] args) 
    {
        List<String> list=new ArrayList<String>();
        list.add("One");
        list.add("Two");
        list.add("Three");

        for (String string : list) 
        {
            System.out.println(string);
            list.remove(string);
        }
    }

Output: One

Exception in thread "main" java.util.ConcurrentModificationException

Again an exception is thrown which is ideal behavior.

But why it runs properly in case-2 without throwing any ConcurrentModificationException.

like image 873
Ankur Lathi Avatar asked Jul 21 '13 12:07

Ankur Lathi


2 Answers

From the documentation (emphasis mine):

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. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

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.

like image 160
abacabadabacaba Avatar answered Nov 20 '22 20:11

abacabadabacaba


The previous answers posted show you the relevant docs that explain why this is appropriate behavior; you are not guaranteed to receive that exception.

If you're really interested in seeing why you don't receive it with only two elements (or really, if you remove the next to last element regardless of the size), you can look at the source code for ArrayList.

Your loop:

for (String string : list)

is actually:

for(Iterator<String> i = list.iterator(); i.hasNext(); ) {
    String string = i.next();
    ...
}

Internally in Arraylist there is an int size that represents the current number of elements in the ArrayList. It is decremented when you call remove().

Inside the Iterator there is an int cursor that represents the current location (index) of the Iterator. It gets incremented when you call next()

hasNext() in the Iterator checks the current cursor position against the size.

In your example the chain of events is as follows:

  • cursor starts at 0, size starts at 2
  • next() is called, cursor incremented to 1.
  • remove() is called, size is decremented to 1
  • hasNext() compares cursor to size, finds they are the same, returns false
  • loop exits without throwing exception

So if you remove the next to last element in any size ArrayList while iterating through it you will not receive an exception. (Also worth noting is that you will never process the last element in that list in the loop; your example only prints One for that reason).

Remember though - this is an implementation detail and is not guaranteed. The docs tell you that you should not rely on the exception being thrown (or not thrown). ArrayList could be rewritten in a different way where the above no longer applies and the exception is thrown (and in fact, in another JVM that may well already be the case).

like image 7
Brian Roach Avatar answered Nov 20 '22 22:11

Brian Roach