1. 程式人生 > >ArrayList中ConcurrentModificationException

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--),也不要這麼做,這樣做可能造成我們意想不到錯誤。