在java語言中,ArrayList是一個很常用的類,在編程中經常要對ArrayList進行刪除操作,在使用remove方法對ArrayList進行刪除操作時,報java.util.ConcurrentModificationException異常,下面探討一下該異常的原因以及解決辦法。
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class Test {
5
6 public static void main(String[] args) {
7 // TODO Auto-generated method stub
8 List<Integer> listA=new ArrayList<>();
9 listA.add(1);
10 listA.add(2);
11 listA.add(3);
12 listA.add(4);
13 listA.add(5);
14 listA.add(6);
15
16 for(Integer a:listA){
17 if (a==3) {
18 listA.remove(3);
19 }
20 }
21 }
22 }
上述代碼在刪除value=3的元素時,報java.util.ConcurrentModificationException異常,如下圖
1 Exception in thread "main" java.util.ConcurrentModificationException 2 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) 3 at java.util.ArrayList$Itr.next(ArrayList.java:851) 4 at com.zhang.Test.main(Test.java:19)
那么是不是刪除每一個元素都會報上面的異常呢?經過測試發現,刪除value=5的元素(倒數第二個元素)時,就不會報上面異常,除此之外均會報如上異常。(此處不再一一測試,有興趣可自己編碼測試)
我們通過上述測試發現了規律,既除了刪除倒數第二個錯誤不會異常,刪除其他元素均會異常。既然掌握了規律,那么就要從源碼層面揭露該異常的原因。首先發現Java的for循環,就是將List對象遍歷托管給Iterator,你如果要對list進行增刪操作,都必須經過Iterator,否則Iterator遍歷時會亂,所以直接對list進行刪除時,Iterator會拋出ConcurrentModificationException異常。
其實,每次foreach迭代的時候都有兩部操作:
第一步:iterator.hasNext() //判斷是否有下個元素
public boolean hasNext() {
return cursor != size;
}
第二步:item = iterator.next() //下個元素是什么,並賦值給上面例子中的item變量
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];
}
通過debug調試,我們發現,checkForComodification時返回了異常,異常原因為 modCount != expectedModCount。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
進一步閱讀源碼,發現:
1.modCount 時List從new開始,被修改的次數。當List調用Remove等方法時,modCount++
2.expectedModCount是指Iterator現在期望這個list被修改的次數是多少次。是在Iterator初始化的時候將modCount 的值賦給了expectedModCount
那么就解釋了為什么會報上述異常:
1.modCount 會隨着調用List.remove方法而自動增減,而expectedModCount則不會變化,就導致modCount != expectedModCount。
2.在刪除倒數第二個元素后,cursor=size-1,此時size=size-1,導致hasNext方法認為遍歷結束。
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> listA=new ArrayList<>();
listA.add(1);
listA.add(2);
listA.add(3);
listA.add(4);
listA.add(5);
listA.add(6);
for(Integer a:listA){
System.out.println(a);
if (a==5) {
listA.remove(5);
}
}
}
}
上述代碼加了打印后,輸出1,2,3,4,5;進一步證明最后一個元素6並沒有被遍歷到。
解決方法1:
從API中可以看到List等Collection的實現並沒有同步化,如果在多 線程應用程序中出現同時訪問,而且出現修改操作的時候都要求外部操作同步化;調用Iterator操作獲得的Iterator對象在多線程修改Set的時 候也自動失效,並拋出java.util.ConcurrentModificationException。這種實現機制是fail-fast,對外部 的修改並不能提供任何保證。
網上查找的關於Iterator的工作機制。Iterator是工作在一個獨立的線程中,並且擁有一個 mutex鎖,就是說Iterator在工作的時候,是不允許被迭代的對象被改變的。Iterator被創建的時候,建立了一個內存索引表(單鏈表),這 個索引表指向原來的對象,當原來的對象數量改變的時候,這個索引表的內容沒有同步改變,所以當索引指針往下移動的時候,便找不到要迭代的對象,於是產生錯 誤。List、Set等是動態的,可變對象數量的數據結構,但是Iterator則是單向不可變,只能順序讀取,不能逆序操作的數據結構,當 Iterator指向的原始數據發生變化時,Iterator自己就迷失了方向。
如何才能滿足需求呢,需要再定義一個List,用來保存需要刪除的對象:
List delList = new ArrayList();
最后只需要調用集合的removeAll(Collection con)方法就可以了。
解決方法2:
在找到原因后,則進一步進行解決
經過查閱源碼可以發現,iterator也有一個remove方法如下,其中有一個重要的操作為expectedModCount = modCount;這樣就保證了兩者的相等。
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();
}
}
修改后的代碼如下:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> listA=new ArrayList<>();
listA.add(1);
listA.add(2);
listA.add(3);
listA.add(4);
listA.add(5);
listA.add(6);
Iterator<Integer> it_b=listA.iterator();
while(it_b.hasNext()){
Integer a=it_b.next();
if (a==4) {
it_b.remove();
}
}
for(Integer b:listA){
System.out.println(b);
}
}
}
java.util.ConcurrentModificationException異常完美解決。
