ArrayList中ConcurrentModificationException
java中兩種基本的集合結構ArrayList和LinkedList底層有兩種不同的儲存方式實現,ArrayList為陣列實現,屬於順序儲存,LinkedList為連結串列實現,屬於鏈式儲存,在對ArrayList做迭代刪除時,會出現ConcurrentModificationException
public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("aa"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String next = iterator.next(); list.remove(next); } }
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.baicells.linked.list.ListTest.main(ListTest.java:23)
但如果在list再新增一個元素,如bb,此時list.size = 2,上述程式碼執行結束後,list中只有bb,雖然與預期結果不一致,但並沒有出現ConcurrentModificationException,當再次向集合中新增更多元素時,又出現了ConcurrentModificationException,使用的jdk版本為1.8.
ArrayList原始碼中,方法iterator()返回了Itr物件
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>. * *@return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr(); }
Itr為ArrayList內部類,主要關注hasNext,next,checkForComodification方法
/** * An optimized version of AbstractList.Itr */ 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; Itr() {} 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(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
modCount在add和remove時都會執行++操作,這個可在ArrayList原始碼中找到出處
在Ite類中,將expectedModCount 的大小初始化為modCount,當執行hashNex和nex時,都不會使modCount的值發生變化
當list中只有一個數據時,此時cursor=0,size=1,hasNex返回true,在next方法中校驗modCount和expectedModCount是否相等,如果不相等,則丟擲併發修改異常,如果相等,對cursor做了+1操作
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
此時兩者是相等的,都為1,然後執行remove操作刪除該資料
public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
remove是對modCount做了++操作,並且對size做了--操作,while迴圈繼續,hasNext,cursor=1,size=0,兩者不相等,則進入到迴圈,執行next操作
此時modCount在上述remove操作時已經做了++操作,expectedModCount的值卻沒有變化,故modCount和expectedModCount是不相等的,因此丟擲ConcurrentModificationException
當輸入兩個引數時為什麼就不會報錯了,雖然結果不是預期的清空了集合?
當集合為2時,在第一次刪除後,各關鍵屬性值分別為cursor=1,modCount在add兩次後變為2,在remove一次後變為3,expectedModCount=2,size在remove後--,變為1,故此時在while迴圈hasNext中對比cursor!=size,返回false,while迴圈結束,繼續向下走,所以最後集合中剩下了第二次add的結果,第一次add結果被刪除,程式也沒有出現ConcurrentModificationException異常。
當輸入三個引數或者更多時,會怎樣?
繼續按照上述思路分析,當集合中有三個元素時,在第一次刪除後,各關鍵屬性值分別為cursor=1,modCount在add三次後變為3,在remove一次後變為4,expectedModCount=3,size在remove後--,變為2,此時while迴圈中cursor!=size返回true,進入while迴圈,next方法中檢測到modCount != expectedModCount返回false,則丟擲ConcurrentModificationException
當為更多元素時,在第二次進入到next方法後,都將丟擲ConcurrentModificationException,只有在陣列元素個數為2時,才不會發生ConcurrentModificationException,但結果也不是我們預期的
綜上,不要在迭代集合時刪除元素,即使是foreach或者普通for迴圈(普通for迴圈或者foreach也會造成size--),也不要這麼做,這樣做可能造成我們意想不到錯誤。