java.util.ConcurrentModificationException異常原因及解決方法


在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異常完美解決。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM