使用迭代器遍歷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支持併發訪問,所以同時進行迭代和修改是沒有問題的。

我喜歡,駕馭着代碼在風馳電掣中創造完美!我喜歡,操縱着代碼在隨必所欲中體驗生活!我喜歡,書寫着代碼在時代浪潮中完成經典!每一段新的代碼在我手中誕生對我來說就象觀看剎那花開的感動!
歡迎分享與轉載
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章