快速失敗,是java集合中的一種錯誤檢測機制。當多個線程對集合進行結構上的改變操作時,就有可能會產生fail-fast機制。例如存在兩個線程1和2,線程1通過Iterator在遍歷集合中元素時,線程2修改了集合的結構(添加或者刪除元素),這個時候就會拋出ConcurrentModificationException異常,從而產生了fail-fast機制。
併發修改異常ConcurrentModificationException:方法檢測到對象的併發修改時,但是不允許這種修改,就會拋出該異常。單線程和多線程時都有可能產生這種異常。
分析ArrayList源碼可知,迭代器在調用next() 和remove()方法時,都會調用checkForComodification()方法,該方法主要就是檢測modCount和expectedModCount是否相等。如果不相等則拋出異常,從而產生了fail-fast機制。
modCount用來記錄集合修改的次數,每修改一次(添加或者刪除),modCount++。
具體分析如下:
線程1在遍歷集合A。調用了集合的iterator方法。
public Iterator<E> iterator() {
return new Itr();
}
Itr是ArrayList的內部類,實現了Iterator接口
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = modCount;
………省略
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
………省略
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
………省略
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
………省略
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
由上述可知,在調用remove和next方法時都會調用checkForComodification()方法。
看看checkForComodification()方法的實現,代碼如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
這裏進行了modCount 和expectedModCount的比較。如果不相等,則拋出ConcurrentModificationException異常。
下面分析一下什麼時候纔會不相等?
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
在新建itr對象時,會把當前的modCount的值傳遞給expectedModCount,之後, expectedModCount的值就不會再改變。因此下面要分析一下modCount什麼時候會發生改變?以add方法爲例
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
//修改modCount的值
this.modCount = parent.modCount;
this.size++;
}
觀察源碼可知,add remove clear方法,只要涉及到修改集合結構時,就會改變modCount的值。
繼續剛纔線程1的執行,假設這時線程2執行了add方法,向集合中添加了一個元素,此時modCount++,線程1接着遍歷,在執行到next函數時,調用checkForComodification方法比較expectedModCount和modCount的值,發現不相等了,就會拋出併發修改異常。從而產生了fail-fast機制。
如何解決fail-fast呢?或者解決併發修改異常呢?
可以使用同步來解決,在客戶端調用會改變modCount值的方法時,加synchronized,或者直接使用Collections.synchronizedList類。
還可以使用jdk5提供的CopyOnWriteArrayList類。
CopyOnWriteArrayList僅僅實現了List集合接口,並沒有繼承AbstractList抽象類。ArrayList的iterator()方法是繼承了AbstractList,但是CopyOnWriteArrayList是自己實現了iterator。最主要的原因是CopyOnWriteArrayList的Iterator實現類中沒有checkForComodification方法,所以不會拋出併發修改異常。
那CopyOnWriteArrayList實現的原理是什麼?以add方法爲例
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
關鍵就在於
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
這三行代碼。它是先對原先的數組進行復制,然後在複製之後的數組上進行添加元素,最後在改變原有數據的引用即可。怪不得該類叫做CopyOnWriteArrayList,無疑是先複製再進行寫操作!