天天看点

Java集合之ConcurrentModificationException(并发修改异常)分析

前言

今天写LeetCode遇到一道题,我想利用作为方法参数的一个集合作为返回的值,来达到节省空间的目的:

意思就是,我想对集合intervals进行修改,然后返回值就传修改后的intervals。

然后顺势就联想到了并发修改异常,之前只是知道个概念,并没有仔细思考过。今天就来分析一下ConcurrentModificationException。

ConcurrentModificationException

ConcurrentModificationException是开发中一个常见的异常,多发生于对一个Collection进行边遍历边做影响size变化的操作时,比如说遍历集合同时向集合中添加新的元素。

举例

下面我们进行三种操作:

1.利用for循环遍历集合的同时添加元素

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

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

没有问题。

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

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

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

出现了问题,编译器提示:Exception in thread “main” java.util.ConcurrentModificationException。

3.利用超级for循环遍历集合的同时添加元素

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

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

同样出现了问题,编译器提示: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 == ) {
                throw new NoSuchElementException();
            }
            remaining = rem - ;
            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 / ) ?
                     MIN_CAPACITY_INCREMENT : s >> )];
            System.arraycopy(a, , newArray, , s);
            array = a = newArray;
        }
        a[s] = object;
        size = s + ;
        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();
        list.add();
        list.add();
        ListIterator<Integer> listIterator = list.listIterator();
        while (listIterator.hasNext()) {
            if (listIterator.next().equals()) {
                listIterator.add();
            }
        }
           

不会出现问题。

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

其次它会将expectedModCount的值与modCount的值进行同步,具体可以去查看源码,这里就不做分析了。

参考:Java并发修改错误ConcurrentModificationException分析