fail-fast是在操作迭代器时产生的
ArrayList中无论add、remove、clear方法只要是涉及了改变ArrayList元素的个数的方法都会导致modCount的改变。所以我们这里可以初步判断由于expectedModCount 得值与modCount的改变不同步,导致两者之间不等从而产生fail-fast机制
private class Itr implements Iterator<E> {
int cursor; // 游标初始位置在数组下标为0的前面
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; //List的添加元素的时候,modCount++
Itr() {}
public boolean hasNext() {
// 游标从0开始,只要不等于数组中存储的数据的长度,就判断有下一个
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")
final void checkForComodification() {
//expectedModCount为最开始的modCount,只要后期list中元素个数发生变化,modCount!=expectedModCount,导致抛出异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
迭代器在调用next()、remove()方法时都是调用checkForComodification()方法,它检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 异常,从而产生fail-fast机制。
expectedModCount 是在IteratorTest中定义的:int expectedModCount = ArrayList.this.modCount;所以他的值是不可能会修改的,所以会变的就是modCount。modCount是在 AbstractList 中定义的,为全局变量:
protected transient int modCount = 0;
有两种解决方案:
方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList(不推荐)
方案二:使用CopyOnWriteArrayList来替换ArrayList。
CopyOnWriteArrayList为什么能解决这个问题呢?CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为了避免Copy出N个副本出来,导致并发写。但是。CopyOnWriteArrayList中的读方法是没有加锁的。