迭代器的 ConcurrentModificationException
在使用迭代器遍历 HashMap, ArrayList时碰到了这个异常,
碰到异常莫得慌,仔细分析,不难发现,出现异常的语句是: map.remove(key)。
解决方案:如果程序要求是线程安全的, 那么可以使用安全的集合 (ConcurrentHashMap), 但如果说只是简单的实现遍历, 可以使用迭代器的删除方法 : its.remove() 删除当前元素
那么为什么会出现该异常呢??
我们都知道 HashMap, ArrrayList 都是线程不安全的,
HashMap的成员属性 modCount : 表示修改结构的次数,当一个线程读, 一个线程写时,modCount与期望值不相同,就会触发快速失败,立即报告错误, 抛出异常。
具体分析:HashMap中:
KeyIterator类 继承了 HashIterator 类, 查看 HashIterator 类
Node<K,V> next; // 下一个entry的位置
Node<K,V> current; // 当前 entry
int expectedModCount; // 期望值, 若不满足,触发快速失败
int index; // 当前位置
遍历时会判断 当前 modCount 与 期望值是否相同,
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount) //判断 当前 modCount 与 期望值是否相同,
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc) //判断 当前 modCount 与 期望值是否相同,
throw new ConcurrentModificationException();
}
}
HashMap中的 remove 方法, 会直接修改modCount的值, 导致与期望值不同,抛出异常, 那么 迭代器的 remove方法为什么不会抛出异常呢?
这是因为 该方法在更新 modCount时,同步更新了 期望值
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount; //同步更新期望值
}
继续看ArrayList,
ArrayList中的remove方法,都会修改了 modCount
public E remove(int index) {
rangeCheck(index);
modCount++; 修改了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;
}
一样的逻辑,ArrayList中 Itr类中的next 方法,会检查modCount是否一致,
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; // 返回最后一个元素的索引;如果没有返回,则返回-1
int expectedModCount = modCount; //期望值
查看: checkForComodification方法
final void checkForComodification() {
if (modCount != expectedModCount) // 判断
throw new ConcurrentModificationException();
}
}
Itr类中的remove方法, 会同步更新 期望值, 避免快速失败
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();
}
}