使用迭代器遍歷List的時候修改List報ConcurrentModificationException異常原因分析


  在使用Iterator來迭代遍歷List的時候如果修改該List對象,則會報java.util.ConcurrentModificationException異常,下面看一個例子演示:

 1 package com.others;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5 import java.util.List;
 6 import java.util.concurrent.CopyOnWriteArrayList;
 7 
 8 public class ArrayListTest {
 9 
10     public static void main(String[] args) {
11         List<String> list = new ArrayList<String>();
12         //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
13         list.add("a");
14         list.add("b");
15         list.add("c");
16         list.add("d");
17         list.add("e");
18         Iterator iterator = list.iterator();
19         while(iterator.hasNext()){
20             String str = (String) iterator.next();
21             if(str.equals("c")){
22                 list.remove(str);
23             }else{
24                 System.out.println(str);
25             }
26         }
27     }
28 
29 }

  結果為:

a
b
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at com.others.ArrayListTest.main(ArrayListTest.java:20)

 

  當調用list的iterator()方法的時候,返回的是一個Itr對象(實現了Iterator接口):

1 public Iterator<E> iterator() {
2         return new Itr();
3     }

  我們看一下Itr這個類:

 1 private class Itr implements Iterator<E> {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount; //剛創建迭代對象的時候List的modCount
 5 
 6         public boolean hasNext() {
 7             return cursor != size;
 8         }
 9 
10         @SuppressWarnings("unchecked")
11         public E next() {
12             checkForComodification(); //每調用一次next()函數都會調用checkForComodification方法判斷一次
13             int i = cursor;
14             if (i >= size)
15                 throw new NoSuchElementException();
16             Object[] elementData = ArrayList.this.elementData;
17             if (i >= elementData.length)
18                 throw new ConcurrentModificationException();
19             cursor = i + 1;
20             return (E) elementData[lastRet = i];
21         }
22 
23         public void remove() {
24             if (lastRet < 0)
25                 throw new IllegalStateException();
26             checkForComodification();
27 
28             try {
29                 ArrayList.this.remove(lastRet);
30                 cursor = lastRet;
31                 lastRet = -1;
32                 expectedModCount = modCount;
33             } catch (IndexOutOfBoundsException ex) {
34                 throw new ConcurrentModificationException();
35             }
36         }
37         //此方法用來判斷創建迭代對象的時候List的modCount與現在List的modCount是否一樣,不一樣的話就報ConcurrentModificationException異常
38         final void checkForComodification() {
39             if (modCount != expectedModCount)
40                 throw new ConcurrentModificationException();
41         }
42     }

  List對象有一個成員變量modCount,它代表該List對象被修改的次數,每對List對象修改一次,modCount都會加1.

  Itr類里有一個成員變量expectedModCount,它的值為創建Itr對象的時候List的modCount值。用此變量來檢驗在迭代過程中List對象是否被修改了,如果被修改了則拋出java.util.ConcurrentModificationException異常。在每次調用Itr對象的next()方法的時候都會調用checkForComodification()方法進行一次檢驗,checkForComodification()方法中做的工作就是比較expectedModCount 和modCount的值是否相等,如果不相等,就認為還有其他對象正在對當前的List進行操作,那個就會拋出ConcurrentModificationException異常。

  我們再來分析一下上面那個例子,當例子程序執行到22行的時候,將list對象里面的“c"刪除了,同時list對象的modCount值加1,但是Itr對象的expectedModCount沒有變,他們肯定是不相等了。等再一次執行next()方法的時候調用了checkForComodification()方法,這時候就拋出異常了。

  我們再將上面那個例子稍微改動一下:將21行改為if(str.equals("d")){,即刪除”d"這個元素。運行結果如下:

a

b

c

  這時卻沒有報異常了,但是“e"卻沒有輸出來,這是為什么呢?原因很簡單,我們看到Itr的hashNext()方法:

1 public boolean hasNext() {
2             return cursor != size;
3         }

  它是通過Itr的對象的cursor與List對象的size值來判斷是否還有未迭代的對象,當遍歷完“d"的時候cursor=4,刪除”d"的時候,List對象的size就會減1,size首先為5,后來變為4,這時候cursor和size是相等的,hasNext()方法返回的是false,就認為遍歷結束了,所以刪除以后沒有進去執行next()方法了,就沒有拋出異常了,當然"e"也沒有輸出來。

  為了避免這種異常,我們可以使用CopyOnWriteArrayList來代替ArrayList,CopyOnWriteArrayList支持並發訪問,所以同時進行迭代和修改是沒有問題的。


免責聲明!

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



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