通过ArrayList对modCount的操作分析fail-fast 机制
AbstractList类中有一个属性
protected transient int modCount = 0;
api中对它的描述是:
-
此列表已被结构修改的次数。 结构修改是改变列表大小的那些修改,或以其他方式扰乱它,使得正在进行的迭代可能产生不正确的结果。
- 该字段由迭代器和列表迭代器实现使用,由
iterator
和listIterator
方法返回。 如果该字段的值意外更改,迭代器(或列表迭代器)将抛出一个ConcurrentModificationException
响应next
,remove
,previous
,set
或add
操作。 这提供了fail-fast行为,而不是面对在迭代期间的并发修改的非确定性行为
我的理解是,modCount表示了当前列表结构被修改的次数,在调用迭代器操作时,则会检查这个值,如果发现已更改,抛出异常
不过需要注意的是transient修饰意味着这个属性不会被序列化,而且modCount并没有被voliate修饰,也就是它不能保证在线程之间是可见的
从ArraytList源码中可以发现,add,remove,clear等方法实现时,均添加了modCount++;操作,例如clear方法:
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
在arraylist中调用迭代器是通过内部类实现的:
public Iterator<E> iterator() {
return new Itr();
}
在这个内部类中,同样维护了一个类似modCount的变量
int expectedModCount = modCount;
并提供了检测方法
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个检测方法在迭代器中类似next方法里面作为首先需要判断的条件
@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];
}
我们综合以上,就可以得出,
在使用迭代器遍历arraylist时,会初始化一个和modCount相等的变量,如果在迭代过程中,arraylist中发生了类似add这种改变结构的操作(modCount改变),导致modCount != expectedModCount,那么会抛出一个异常ConcurrentModificationException,即产生fail-fast事件
产生fail-fast有两种原因:
1.单线程情况下,迭代的过程中,调用类似add方法,但是一般不会这样做
ArrayList<Integer> al = new ArrayList<>();
for(int i=0;i<10;i++)
al.add(i);
Iterator<Integer> it = al.iterator();
while(it.hasNext()) {
System.out.println(it.next());
if(!it.hasNext())
al.add(10);
}
2.主要发生在多线程情况下,例如让线程1迭代,线程2修改,就有可能会出现
final ArrayList<Integer> al = new ArrayList<>();
for(int i=0;i<10;i++)
al.add(i);
new Thread(new Runnable() {
@Override
public void run() {
Iterator<Integer> it = al.iterator();
while(it.hasNext()) {
System.out.print(it.next()+" ");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
al.remove(6);
}
}).start();
如果抛出异常,就类似这个样子:
0 1 Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.rw.importword.utils.Text$1.run(Text.java:19)
at java.lang.Thread.run(Thread.java:748)
但是也有可能不抛出异常:
根据输出结果来看,al的结构已经改变,但是没有抛出异常
至于原因我想是:
modCount没有被voliate修饰,在线程之间不可见,可能某一个时机,线程2中remove操作改变的modCount值并没有及时写到内存中,线程1中迭代器获取的modCount值仍然是之前的值
由此可以得出,fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。