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

来源:互联网 发布:mac免费游戏推荐 编辑:程序博客网 时间:2024/06/05 01:59

前言

今天写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分析

阅读全文
0 0
原创粉丝点击