迭代器的 ConcurrentModificationException

weixin_42840266發表於2020-10-11

迭代器的 ConcurrentModificationException

在使用迭代器遍歷 HashMap, ArrayList時碰到了這個異常,異常
碰到異常莫得慌,仔細分析,不難發現,出現異常的語句是: map.remove(key)。
解決方案:如果程式要求是執行緒安全的, 那麼可以使用安全的集合 (ConcurrentHashMap), 但如果說只是簡單的實現遍歷, 可以使用迭代器的刪除方法 : its.remove() 刪除當前元素

那麼為什麼會出現該異常呢??
我們都知道 HashMap, ArrrayList 都是執行緒不安全的,
HashMap的成員屬性 modCount : 表示修改結構的次數,當一個執行緒讀, 一個執行緒寫時,modCount與期望值不相同,就會觸發快速失敗,立即報告錯誤, 丟擲異常。

具體分析:HashMap中:
KeyIterator類 繼承了 HashIterator 類, 檢視 HashIterator 類

        Node<K,V> next;        // 下一個entry的位置
        Node<K,V> current;     // 當前 entry
        int expectedModCount;  // 期望值, 若不滿足,觸發快速失敗
        int index;             // 當前位置

遍歷時會判斷 當前 modCount 與 期望值是否相同,

        final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)  //判斷 當前 modCount 與 期望值是否相同,
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key, e.value);
            }
           if (modCount != mc)  //判斷 當前 modCount 與 期望值是否相同,
                throw new ConcurrentModificationException();
        }
    }

HashMap中的 remove 方法, 會直接修改modCount的值, 導致與期望值不同,丟擲異常, 那麼 迭代器的 remove方法為什麼不會丟擲異常呢?
這是因為 該方法在更新 modCount時,同步更新了 期望值

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount; //同步更新期望值
        }

繼續看ArrayList,
ArrayList中的remove方法,都會修改了 modCount

    public E remove(int index) {
        rangeCheck(index);

        modCount++;  修改了modCount
        E oldValue = elementData(index);

        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

        return oldValue;
    }

一樣的邏輯,ArrayList中 Itr類中的next 方法,會檢查modCount是否一致,

        int cursor;       // 要返回的下一個元素的索引
        int lastRet = -1; // 返回最後一個元素的索引;如果沒有返回,則返回-1
        int expectedModCount = modCount; //期望值

檢視: checkForComodification方法

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

Itr類中的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();
            }
        }

相關文章