foreach + remove = ConcurrentModificationException


foreach + remove = ConcurrentModificationException


问题:

List list = new ArrayList<>();
list.add("0");
list.add("1");
list.add("2");
list.add("3");
for (String s : list) {
    if (条件语句) {
        list.remove(s);
    }
}

在上述代码的 if 语句中依次使用下列条件语句并执行代码:

条件语句 执行结果
"0".equals(s) 抛出 java.util.ConcurrentModificationException 异常
"1".equals(s) 抛出 java.util.ConcurrentModificationException 异常
"2".equals(s) 正常执行
"3".equals(s) 抛出 java.util.ConcurrentModificationException 异常

分析:

对代码进行反编译,可以发现 foreach 只是一个语法糖,最终需要转换为 iterator 进行实现:

ArrayList list = new ArrayList();
list.add("0");
list.add("1");
list.add("2");
list.add("3");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    String s = (String)iterator.next();
    if (!"2".equals(s)) continue;
    list.remove(s);
}

ArrayList 存在一个名为 Itr 的实现了 Iterator 接口的内部类,通过 iterator() 方法可以获取该内部类的实例:

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable {

    transient Object[] elementData;
    
    private int size;
    
    protected transient int modCount = 0;
    
    public Iterator iterator() { return new Itr(); }
    
    private class Itr implements Iterator {

        int cursor = 0;

        int lastRet = -1;

        int expectedModCount = modCount;

        public boolean hasNext() {...}

        public E next() {...}

        public void remove() {...}

        final void checkForComodification() {...}
    }
}
  • elementData

    集合中存储的所有元素,elementDate 的数组长度代表了 集合的容量

  • size

    集合包含元素的个数

  • modCount

    集合发生“结构性修改”的次数。对集合进行结构性修改的常见方式为调用add()、remove()等方法改变集合包含的元素数量。

    ArrayList list = new ArrayList(); // modCount == 0
    list.add("0"); // modCount == 1
    list.add("1"); // modCount == 2
    list.add("2"); // modCount == 3
    list.add("3"); // modCount == 4
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        String s = (String)iterator.next();
        if (!"2".equals(s)) continue;
        list.remove(s); // modCount == 5
    }
    

    就本文分析的代码而言,创建 ArrayList 实例时,modCount 的初值为0,即尚未对集合进行任何修改,在调用了4次 add() 方法后,modCount的值就累加到了4。

  • cursor

    当前遍历到的集合元素的下标。

  • lastRet

    上一次遍历到的集合元素的下标。

  • expectedModCount

    在只有当前迭代器对集合进行“结构性修改”的情况下,集合应该具有的结构性修改次数。

  • checkForComodification()

    检查集合是否发生“并发修改”。所有并发修改,指的是除了当前迭代器,外界也对集合进行了修改。由于 ArrayList 没有对并发访问进行控制,因此并发修改将使得当前迭代器后续的行为变得不可控。

    就 Itr 而言,它通过比较 modCount 和 expectedModCount 来判断是否还有元素尚未遍历。

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

    判断是否还有元素尚未遍历。就 Itr 而言,它通过比较 cusor 和 size 是否相等来判断是否还有元素尚未遍历。

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

    向后遍历一个集合元素。

    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];
    }
    
  • remove()

    从集合中移除上一次调用 next() 方法遍历到的元素。

    public void remove() {
        // 每调用一次 next() 方法 最多只能调用一次 remove() 方法。
        if (lastRet < 0)
            throw new IllegalStateException();    
        
        checkForComodification();
    
        try {
            ArrayList.this.remove(lastRet);  // 从集合中移除元素,remove方法会更新modCount的值。
            cursor = lastRet;
            lastRet = -1;                    // 每调用一次 next() 方法 最多只能调用一次 remove() 方法。
            expectedModCount = modCount;     // 记录新的modCount。
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    
条件语句 代码序列 执行结果
"0".equals(s) hashNext(): true
cusor = 0,size = 4

next(): “0”
checkForComodification(): OK
modCount = 4,expectedModCount = 4
cusor = 1

”0”.equals(“0”): true
remove(): OK
modCount = 5,size = 3

hashNext(): true
cusor = 1,size = 3

next(): Exception
checkForComodification(): Exception
modCount = 5,expectedModCount = 4
java.util.ConcurrentModificationException
"1".equals(s) java.util.ConcurrentModificationException
"2".equals(s) hashNext(): true
cusor = 2,size = 4

next(): “2”
checkForComodification(): OK
modCount = 4,expectedModCount = 4
cusor = 3

”2”.equals(“2”): true
remove(): OK
modCount = 5,size = 3

hashNext(): false
cusor = 3,size = 3

正常执行
"3".equals(s) hashNext(): true
cusor = 3,size = 4

next(): “3”
checkForComodification(): OK
modCount = 4,expectedModCount = 4
cusor = 4

”3”.equals(“3”): true
remove(): OK
modCount = 5,size = 3

hashNext(): true
cusor = 4,size = 3

next(): Exception
checkForComodification(): Exception
modCount = 5,expectedModCount = 4
java.util.ConcurrentModificationException

上面4个条件语句都在迭代器外部对集合进行了结构性修改,不同之处在于,"2".equals(s) 条件语句对集合进行结构行修改以后,就停止了在迭代器中修改集合,这就避免了冲突的发生,所以没有抛出异常。

附件:

完整源码:

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable {
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
    
    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
     private int size;
    
    /**
     * The number of times this list has been structurally modified.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * 

This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * fail-fast behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * *

Use of this field by subclasses is optional. If a subclass * wishes to provide fail-fast iterators (and list iterators), then it * merely has to increment this field in its {@code add(int, E)} and * {@code remove(int)} methods (and any other methods that it overrides * that result in structural modifications to the list). A single call to * {@code add(int, E)} or {@code remove(int)} must add no more than * one to this field, or the iterators (and list iterators) will throw * bogus {@code ConcurrentModificationExceptions}. If an implementation * does not wish to provide fail-fast iterators, this field may be * ignored. */ protected transient int modCount = 0; public Iterator iterator() { return new Itr(); } /** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} 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(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } }