foreach + remove = ConcurrentModificationException
问题:
List<String> 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<String> list = new ArrayList<String>();
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<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
transient Object[] elementData;
private int size;
protected transient int modCount = 0;
public Iterator<E> iterator() { return new Itr(); }
private class Itr implements Iterator<E> {
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<String> list = new ArrayList<String>(); // 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<E> extends AbstractList<E> implements List<E>, 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 <i>structurally modified</i>.
* 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.
*
* <p>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
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> 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<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() {}
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();
}
}
}