Why can't an JAVA collection add or delete elements in an foreach loop

  • 2021-09-16 06:57:51
  • OfStack

Directory 1. Encoding Mandatory Specification 2. Cause Analysis 3. Related Knowledge Introduction 3.1. What is Fast Failure (fail-fast)? 3.2. What is a security failure (fail-safe)?

1. Coding enforcement protocol

In Alibaba Java Development Manual, there is one regulation for collective operation, as follows:

"Force" not to perform remove/add operations on elements in an foreach loop. Use Iterator mode for remove elements, and lock Iterator objects if concurrent operations occur.


public class SimpleTest {
    public static void main(String[] args) {
        List<String> list = Lists.newArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
 
        // Positive example 
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if ("1".equalsIgnoreCase(item)) {
                iterator.remove();
            }
        }
 
        // Counterexample 
        for (String item : list) {
            if ("2".equals(item)) {
                list.remove(item);
            }
        }
    }
}

2. Cause analysis

When you loop or iterate, you first create an iteration instance whose expectedModCount is assigned to the collection's modCount.

Every time the iterator makes hashNext ()/next () traverse the next element, it will detect whether the modCount variable is equal to the expectedModCount value, and if it is equal, it will return traversal; Otherwise, the exception "ConcurrentModificationException" is thrown, and the traversal ends

If an element is added or removed from the loop, the add and remove methods of the collection are called directly "causing modCount to increase or decrease", but these methods do not modify expectedModCount in the iteration instance, resulting in the value of expectedModCount and modCount being not equal in the iteration instance, and an ConcurrentModificationException exception is thrown

However, remove and add methods in the iterator will re-assign expectedModCount to modCount after calling remove and add methods of the collection, so adding and deleting elements in the iterator can run normally.

You can refer to the source code of the internal private classes Itr and ListItr in ArrayList


public Iterator<E> iterator() {
        return new Itr();
    }
 
/**
     * An optimized version of AbstractList.Itr
     */
    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;
 
        Itr() {}
 
        // Deleted 1 Some codes 
 
        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();
        }
 
  }
   public E remove(int index) {
        rangeCheck(index);
 
        modCount++;
        E oldValue = elementData(index);
 
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
 
        return oldValue;
    }

3. Introduction of relevant knowledge

3.1. What is a quick failure (fail-fast)?

Fast Failure (fail-fast) is an error detection mechanism of the Java set. A collection class that fails to operate safely under multithreaded conditions (fail-safe) may trigger the fail-fast mechanism, causing an ConcurrentModificationException exception to be thrown, while the iterator is traversing the collection.

In addition, on a single thread, the fail-fast mechanism will also be triggered if the contents of the collection object are modified during traversal.

For example: Under multi-threading, if Thread 1 is traversing the collection, Thread 2 modifies (adds, deletes and modifies) the collection at this time, or Thread 1 modifies the collection during traversal, it will cause Thread 1 to throw ConcurrentModificationException exception.

3.2. What is a security failure (fail-safe)?

The collection container adopting the security failure mechanism is not directly accessed on the collection content during the traversal, but first copies the original collection content and traverses on the collection of copying. Therefore, modifications made to the original set during traversal cannot be detected by the iterator, so ConcurrentModificationException exception will not be thrown.


Related articles: