Java集合之ConcurrentModificationException(併發修改異常)分析

前言

今天寫LeetCode遇到一道題,我想利用作爲方法參數的一個集合作爲返回的值,來達到節省空間的目的:

public List<Interval> merge(List<Interval> intervals) {}

意思就是,我想對集合intervals進行修改,然後返回值就傳修改後的intervals。

然後順勢就聯想到了併發修改異常,之前只是知道個概念,並沒有仔細思考過。今天就來分析一下ConcurrentModificationException

ConcurrentModificationException

ConcurrentModificationException是開發中一個常見的異常,多發生於對一個Collection進行邊遍歷邊做影響size變化的操作時,比如說遍歷集合同時向集合中添加新的元素

舉例

下面我們進行三種操作:

1.利用for循環遍歷集合的同時添加元素

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) == 2) {
                list.add(10);
            }
        }

沒有問題。

2.利用迭代器遍歷集合的同時添加元素

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            if (iterator.next() == 2) {
                list.add(10);
            }
        }

出現了問題,編譯器提示:Exception in thread “main” java.util.ConcurrentModificationException

3.利用超級for循環遍歷集合的同時添加元素

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        for (int num : list) {
            if (num == 2) {
                list.add(10);
            }
        }

同樣出現了問題,編譯器提示:Exception in thread “main” java.util.ConcurrentModificationException

分析

以上使用的是ArrayList,那麼我們來看一下ArrayList中關於迭代器的源碼(Android SDK 23中的Open JDK源碼):

    @Override public Iterator<E> iterator() {
        return new ArrayListIterator();
    }

    private class ArrayListIterator implements Iterator<E> {
        ……
        /** The expected modCount value */
        private int expectedModCount = modCount;

        @SuppressWarnings("unchecked") public E next() {
            ArrayList<E> ourList = ArrayList.this;
            int rem = remaining;
            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (rem == 0) {
                throw new NoSuchElementException();
            }
            remaining = rem - 1;
            return (E) ourList.array[removalIndex = ourList.size - rem];
        }
        ……
    }

調用iterator()會返回一個ArrayListIterator對象,ArrayListIterator初始化的時候,會將外部類ArrayList(其實是ArrayList的父類AbstractList中的)的成員變量modCount的值賦給expectedModCount

然而我們向集合中添加元素的時候,改變了modCount的值:

    @Override public boolean add(E object) {
        Object[] a = array;
        int s = size;
        if (s == a.length) {
            Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];
            System.arraycopy(a, 0, newArray, 0, s);
            array = a = newArray;
        }
        a[s] = object;
        size = s + 1;
        modCount++;
        return true;
    }

expectedModCount的值沒有變,這時候再調用next(),會走進如下判斷分支:

            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }

拋出ConcurrentModificationException

至於超級for循環遍歷呢?其實for-each是個語法糖,編譯器會把它轉化成迭代器遍歷,所以同樣會出錯。

iterator()不行,我們其實還可以使用listIterator(),它是ArrayList的父類AbstractList中的方法,它返回的是一個FullListIterator對象。我們增刪元素就利用FullListIterator的remove()和add(),如下:

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        ListIterator<Integer> listIterator = list.listIterator();
        while (listIterator.hasNext()) {
            if (listIterator.next().equals(2)) {
                listIterator.add(10);
            }
        }

不會出現問題。

首先它的remove()和add()中調用的就是AbstractList的remove()和add()。

其次它會將expectedModCount的值與modCount的值進行同步,具體可以去查看源碼,這裏就不做分析了。

參考:Java併發修改錯誤ConcurrentModificationException分析

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