ConcurrentModificationException,iterator迭代問題[原始碼分析]

weixin_34253539發表於2017-07-26

單執行緒的Iterator迭代過程中刪除集合元素

public class TestIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        list.add("6");

        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String next = it.next();
            if("3".equals(next)) {
                list.remove(3);
            }
            System.out.println(next);
        }


    }
}

以上程式碼會出現如下異常

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at caoyan.test.TestIterator.main(TestIterator.java:22)

從後往前看:
TestIterator第22行程式碼:

String next = it.next();

我們在執行程式碼22行時呼叫了it.next();這個it是ArrayList呼叫iterator()返回的物件;這個物件的next()方法如下圖:

  public E next() {
            checkForComodification();
            ...

next()方法首先它會呼叫checkForComodification()這個方法,這個方法很簡單,就是比較expectedModCount , modCount 這兩個值是不是相等;不相等就丟擲異常;如下圖:

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

這兩個值為什麼會不相等,它們各自代表什麼含義呢,我們看ArrayList的iterator()方法:

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

    /**
     * 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;

Arraylist的iterator()方法,直接建立了一個私有的內部類Itr的物件;expectedModCount 就是定義在這個Itr中的成員變數,而且,在初始化的時候,將modCount賦值給expectedModCount,說明在剛生成Iterator的時候這兩個值是相等的.modCount是定義在哪裡呢,看下面原始碼

public class ArrayList<E> extends AbstractList<E>
...
protected transient int modCount = 0;

modCount是AbstractList的成員變數,ArrayList繼承下來了.所以modCount代表ArrayList的實際長度.
當我們在iterator的迭代過程中改變了ArrayList的長度,就導致modCount變了,而expectedModCount 沒有變,導致不相等報錯

可是為什麼用iterator的remove方法就不報錯呢,我們看Remove方法():

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

可以看到,ArrayList在刪除元素後,expectedModCount = modCount;重新賦值,讓這兩個值相等了.所以,remove方法不會丟擲這個異常

相關文章