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分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值