上一篇文章我們分析了一些ArrayList的簡單的源碼,在分析的過程中,我們發(fā)現(xiàn)在調(diào)用add()、remove()和clear()及其同類方法時,ArrayList的modCount屬性都要加1副女,調(diào)用clone()方法時,新的數(shù)組的modCount屬性要置為0蚣旱。這里就很奇怪了碑幅,這個modCount是什么戴陡?他參與了哪些與ArrayList有關的操作?把他加1的意義是什么沟涨?帶著這些問題恤批,我們開始本篇文章的內(nèi)容。
從ArrayList中的modCount入手
要知道一個屬性的作用裹赴,就要找到它是在哪里被聲明的喜庞。點擊ArrayList的modCout,我們會進入到AbstractList中棋返。這里延都,就是modCount屬性被聲明的地方。ArrayList中的modCount是其繼承的AbstractList的一個屬性睛竣。
在AbstractList中對modCount的解釋為:modCount表示此列表結(jié)構(gòu)已被修改的次數(shù)晰房。 結(jié)構(gòu)修改是指一些改變列表大小或以其他方式擾亂它的方式,會導致正在進行的迭代可能產(chǎn)生不正確的結(jié)果射沟。
正在進行的迭代殊者?通過集合的第一篇文章,我們知道ArrayList向上追溯是實現(xiàn)了Collection接口的验夯,而Collection接口又繼承了Iterable接口來實現(xiàn)對集合中元素的遍歷幽污。在Iterable接口中,獲取迭代器的方法是iterator()方法簿姨。那我們看一下ArrayList所實現(xiàn)的iterator()方法方法做了些什么
public Iterator<E> iterator() {
return new Itr();
}
我們看到它返回了一個Itr的實例。這個Itr又是什么簸搞,我們繼續(xù)看下去:
//此處將ArrayList的modCount也放進來方便分析
protected transient int modCount = 0;
//Itr是Iterator接口的一個實現(xiàn)類
private class Itr implements Iterator<E> {
//獲取ArrayList的數(shù)組大小
protected int limit = ArrayList.this.size;
//下一個元素的索引
int cursor;
//上一次遍歷過的元素的索引; 如果沒有的話返回-1
int lastRet = -1;
//將初始值置為modCount(此屬性很重要扁位,記住它,往下看)
int expectedModCount = modCount;
//判斷是否有下一個元素
public boolean hasNext() {
return cursor < limit;
}
//獲取下一個元素
@SuppressWarnings("unchecked")
public E next() {
//在獲取下一個元素之前趁俊,判斷當前的modCount與expectedModCount是否相等域仇,不等則拋出異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
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();
//在刪除元素之前,判斷當前的modCount與expectedModCount是否相等寺擂,不等則拋出異常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
//判斷下一個元素的索引是否大于數(shù)組的長度暇务,是則拋出異常
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在上面的源碼中,我們注意Itr有三個屬性:cursor怔软、lastRet和expectedModCount垦细。其中,expectedModCount就是我們分析問題的關鍵挡逼。我們發(fā)現(xiàn)expectedModCount在創(chuàng)建Itr實例的時候就會賦初值括改,值為創(chuàng)建Itr實例時的modCount,在Itr創(chuàng)建完成后expectedModCount的值就不再變化了家坎,而modCount呢嘱能?每當ArrayList調(diào)用add()吝梅、remove()和clear()及其相關方法的時候都會加1。所以惹骂,當我們創(chuàng)建了一個Itr實例itr苏携,然后修改了ArrayList結(jié)構(gòu),則使用itr遍歷這個List就會報錯对粪。因為此時expectedModCount 右冻!= modCount。現(xiàn)在想想衩侥,為什么要有這個判斷呢国旷?它預防或者解決了什么問題?
現(xiàn)在茫死,進入本篇文章的主題跪但。
fail-fast機制
fail-fast機制是Java集合中的一個錯誤機制。在創(chuàng)建迭代器之后的任何時候峦萎,除了通過迭代器自己的remove()或add()方法之外屡久,其他導致列表在結(jié)構(gòu)上被修改的行為,都會導致則此類的iterator()和listIterator()方法返回的迭代器fail-fast(快速失敗)爱榔。迭代器將拋出ConcurrentModificationException異常被环。比如,我們看一個簡單的例子:
List<String> list = new ArrayList<>();
list.add("213");
list.add("213");
list.add("213");
Iterator<String> iterator = list.iterator();
list.add("asd");
iterator.next();
本例運行到iterator.next()就會拋出一個ConcurrentModificationException異常详幽,因為我們創(chuàng)建了list的迭代器之后筛欢,又對list的結(jié)構(gòu)做了修改。通過上面的分析唇聘,我們知道此時expectedModCount 版姑!= modCount,所以拋出了異常迟郎。同樣的情況還有多線程操作同一個ArrayList剥险。
現(xiàn)在,我們知道了fail-fast出現(xiàn)的原因和原理宪肖,也就知道了該怎么預防這種情況表制,比如:在多線程的情況下,使用線程安全的集合類來保證線程安全控乾;創(chuàng)建了一個List的Iterator實例后么介,在其完成遍歷之前,不要使用List的add()阱持、remove()和clear()及其相關方法等等夭拌。
好了,本文就分析到這,ending...
(馬上元旦了鸽扁,祝大家(如果有人看的話)也祝自己新的一年有更大的進步K庹馈!桶现!生而有崖躲雅,學無止境)