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機制一定會發生。