單線程情況下集合出現併發修改異常(ConcurrentModificationException)

一:ConcurrentModificationException異常:

當方法檢測到對象的併發修改,但不允許這種修改時,拋出此異常。

二:遍歷list集合時刪除元素出現的異常

public static void main(String[] args) {
    ArrayList<String> list=new ArrayList<String>();
    list.add("111");
    list.add("222");
    list.add("333");
 
    for(Iterator<String> iterator=list.iterator();iterator.hasNext();){
        String ele=iterator.next();
        if(ele.equals("111")) //(1)處
            list.remove("222"); //(2)處
    }
    System.out.println(list);
}

分析代碼:這個一個在使用Iterator迭代器遍歷時,同時用使用remove()方法進行了刪除的操作。

當運行上述代碼時,程序拋出了ConcurrentModificationException異常。

三:分析一下爲何會出現這個異常

1:ConcurrentModificationException異常與modCount這個變量有關。

2:modCount的作用。

modCount就是修改次數,在具體的實現類中的Iterator中才會使用。在List集合中,ArrayList是List接口的實現類,

modCount:表示list集合結構上被修改的次數。(在ArrayList所有涉及結構變化的方法中,都增加了modCount的值)

list結構上別修改是指:改變了list的長度的大小或者是遍歷結果中產生了不正確的結果的方式。add()和remove()方法會是modCount進行+1操作。modCount被修改後會產生ConcurrentModificationException異常, 這是jdk的快速失敗原則。

3:modCount的變量從何而來。

modCount被定義在ArrayList的父類AbstractList中,初值爲0,protected transient int modCount = 0。

4:上述代碼用來4個方法,操作集合,add(),next(),hasNext(),remove(),這四個方法。

(1)ArrayList中add()的源代碼。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

在進行add()方法是,先調用的ensureCapacity()這個方法,來判斷是否需要擴容。

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }//看集合是否爲空集合。很顯然,並不是空集合。
 
    ensureExplicitCapacity(minCapacity); //
}

在判斷是否需要擴容時,用調用ensureExplicitCapacity這個方法,注意,這個方法中對modCount進行的加一操作。

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

所以集合在進行添加元素是會對modCount進行+1的操作。

在進行文章開頭的代碼時list集合中使用add添加了元素後,size=3,modCount=3

(2)Iterator的方法是返回了一個Itr()的類的一個實例。

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

Itr是ArrayList的一個成員內部類。

private class Itr implements Iterator<E> {
    int cursor;       // cursor:表示下一個要訪問的元素的索引
    int lastRet = -1; // 
    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();
        }
    }
    @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();
    }
}for循環內部首先對Iterator進行了初始化,初始化時,expectedModCount=3,cursor=0。

(2.1)單看hasnext()方法
public boolean hasNext() {
    return cursor != size();
}

hasNext是判斷是否右下一個元素,判斷條件是cursor不等於size,有下一個元素。size=3,cursor=0,是有下一個元素的。

(2.2)單看next()方法


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];
}

當進行next()方法時,調用了checkForComodification()方法,進行判斷是否拋出異常

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

此時,modCount=expectedModCount=3,不拋異常,程序繼續進行,next()方法有對cursor進行了加一的操作。cursor=1。

ele=“111”。此時進行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;
}

可以看見進行remove操作時,是通過調用fastRemove()方法進行的實際刪除。在fastRemove()方法中,對modCount進行了+1的操作。

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; 
}

在fastRemove()方法中,modCount進行了+1操作,modCount=4,size進行了-1的操作,size=2,程序繼續進行,cursor=1,size=2,進行next()方法,發現modCount不等於expectedModCount,拋出了ConcurrentModificationException異常。

四:綜上

在for循環對Iterator進行了初始化時,expectedModCount=modCount=3,cursor=0。

進行hasNext()方法,只是判斷,size和cursor的值,並不改變任何值。

進行了next()方法中,先判斷modCount和expectedModCount,後對cursor+1.。改變了cursor的值。

if只是找元素,並不改變值。當找到時,進行remove操作。

進行remove()方法時,modCount+1,size-1。改變了modcount和size的值。

程序會繼續執行:

若是在倒數第二個元素進行刪除操作時,經過前面的遍歷,next()方法將cursor增加至2,只比size=3少1。在

remove()操作時,size-1=2,hasNext()發現cursor=size,程序並沒有遍歷最後一個元素,程序終止。

若是在倒數第2個元素之前進行刪除操作,cursor<size,modCount=4,在next()方法會拋出異常。

若在最後一個元素進行刪除操作,若是沒有進行刪除操作,cursor=size,本該在hasNext()方法中跳出for循環,進行了刪除操作,modCount+1,size-1,使cursor>size,程序會繼續執行next()方法,在next()方法中,判斷modCount不等於expectedModCount,拋出異常。

五:解決方法(遍歷一個集合時如何避免ConcurrentModificationException)

API文檔上也有說的! 在迭代時只可以用迭代器進行刪除!

單線程情況:

(1)使用Iterator提供的remove方法,用於刪除當前元素。

(2)建立一個集合,記錄需要刪除的元素,之後統一刪除。

(3)不使用Iterator進行遍歷,需要之一的是自己保證索引正常。

(4)使用併發集合類來避免ConcurrentModificationException,比如使用CopyOnWriteArrayList,而不是ArrayList。

多線程情況:

(1)使用併發集合類,如使用ConcurrentHashMap或者CopyOnWriteArrayList。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章