問(wèn)題
ArrayList是我們經(jīng)常在代碼中使用的集合類(lèi),但是ArrayList在執(zhí)行remove方法時(shí)會(huì)出現(xiàn)ConcurrentModificationException。針對(duì)這個(gè)問(wèn)題進(jìn)行代碼分析。
例子
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
list.add("h");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
if (str.equals("f")) {
list.remove(str);
}
}
}
結(jié)果:
說(shuō)明:在Debug執(zhí)行過(guò)程中發(fā)現(xiàn),ArrayList在執(zhí)行remove方法移除元素之后,再執(zhí)行it.next這句話拋出了異常。
源碼分析
ArrayList的remove方法:
public E remove(int index) {
// 先檢查下標(biāo)索引是是否越界
rangeCheck(index);
// ArrayList的修改次數(shù)加1
modCount++;
// 獲取索引對(duì)應(yīng)的元素值
E oldValue = elementData(index);
// 獲取刪除元素后啤月,需要移動(dòng)的元素的個(gè)數(shù)
int numMoved = size - index - 1;
if (numMoved > 0)
// 將元素進(jìn)行移動(dòng)拷貝
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 最后將多出的位置設(shè)置為空,這樣說(shuō)明是沒(méi)有引用的對(duì)象了
elementData[--size] = null; // Let gc do its work
// 返回刪除的舊值
return oldValue;
}
說(shuō)明:思想是ArrayList的刪除方法劳跃,將指定位置元素刪除谎仲,然后將當(dāng)前位置后面的元素向前拷貝的方式移動(dòng)。但是注意一點(diǎn)細(xì)節(jié)刨仑,modCount++這步操作郑诺,將ArrayList的修改次數(shù)加1。而后面遍歷時(shí)發(fā)現(xiàn)是通過(guò)使用這個(gè)字段來(lái)判斷贸人,當(dāng)前的集合類(lèi)是否被并發(fā)修改间景。
ArrayList中Iterator迭代器的實(shí)現(xiàn)
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
說(shuō)明:
① 在初始化Itr時(shí)expectedModCount = modCount = 8 。
② 在執(zhí)行next方法的第一步先進(jìn)行了checkForComodification方法的檢查艺智,因?yàn)槲覀冎斑M(jìn)行了remove操作倘要,那么modCount數(shù)值減一,實(shí)際modCount = 7 十拣。
③ modCount 數(shù)值和expectedModCount 數(shù)值不相等封拧,拋出ConcurrentModificationException異常。
正確寫(xiě)法
使用Itr自身提供的remove方法:
public class MyTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
list.add("h");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
if (str.equals("f")) {
it.remove();
}
}
System.out.println(list);
}
}
結(jié)果:
說(shuō)明:使用Itr提供的remove方法夭问,可以發(fā)現(xiàn)expectedModCount是隨著modCount變化而發(fā)生變化的泽西,所以是不需要考慮modCount修改次數(shù)不一致的問(wèn)題。
思考
foreach中的remove方法實(shí)際上使用list.remove一樣會(huì)報(bào)ConcurrentModificationException異常缰趋。因?yàn)閒oreach在jvm中還是會(huì)解析成Iterator來(lái)執(zhí)行的捧杉,實(shí)際上和錯(cuò)誤例子是一樣的效果。