在ArrayList中有個成員變量modCount,繼承於AbstractList。
這個成員變量記錄着集合的修改次數,也就每次add或者remove它的值都會加1。這到底有什么用呢?
先看下面一段測試代碼:
package temp; import java.util.ArrayList; import java.util.List; import java.util.Iterator; public class demo { public static void main(String[] args){ List<String> list = new ArrayList<String>(); //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.add("a"); Iterator iterator = list.iterator(); while(iterator.hasNext()){ String str = (String) iterator.next(); list.remove(str); } } }
在使用迭代器遍歷集合的時候同時修改集合元素。因為ArrayList被設計成非同步的,所以理所當然會拋異常。但是該拋什么異常才能說明該問題呢?
首先得了解ArrayList的迭代器
public Iterator<E> iterator() { return new Itr(); }
在調用list.iterator()的時候返回的是一個Itr對象,它是ArrayList中的一個內部類。
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(); } }
主要關注3個點:
1、expectedModCount的初值為modCount
2、hasNext的判斷條件為cursor!=size,就是當前迭代的位置不是數組的最大容量值就返回true
3、next和remove操作之前都會先調用checkForComodification來檢查expectedModCount和modCount是否相等
如果沒checkForComodification去檢查expectedModCount與modCount相等,這個程序肯定會報ArrayIndexOutOfBoundsException
這樣的異常顯然不是應該出現的(這些運行時錯誤都是使用者的邏輯錯誤導致的,我們的JDK那么高端,不會出現使用錯誤,我們只拋出使用者造成的錯誤,而這個錯誤是設計者應該
考慮的),為了避免出現這樣的異常,定義了檢查。所以拋出ConcurrentModificationException異常更能說明問題。
將測試代碼改成如下:
package temp; import java.util.ArrayList; import java.util.List; import java.util.Iterator; public class demo { public static void main(String[] args){ List<String> list = new ArrayList<String>(); //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); list.add("e"); Iterator iterator = list.iterator(); while(iterator.hasNext()){ String str = (String) iterator.next(); if(str.equals("d")){ list.remove(str); }else{ System.out.println(str); } } } }
輸出卻是 a b c。
因為在刪除 d 的時候cursor為4,size也變成了4。所以hasNext就返回為true了,循環結束,從而后面的元素也不會輸出了。
又想,為什么不把hasNext()的判斷改為cursor <=size()呢?但是我們還有可能 add()這樣的話就會導致數據混亂,事實上線程安全本身就不允許讀的時候被修改。
這種問題在多線程情況下,操作同一集合很容易暴露,就算改成同步的Vector問題還是會存在,需要使用CopyOnWriteArrayList。具體原因下篇博客介紹。