java.util.ConcurrentModificationException异常原因及解决方法

今天项目上报了一些bug,通过查看log发现是ConcurrentModificationException,这个错误之前见过,也知道是遍历list时同时修改了list导致的,但是对其中的原理还不是很清楚,正好借今天的机会学习一下里面的机制。

ArrayList里面有一个变量modCount,专门用来记录该ArrayList被改变了多少次,增删、排序、replace这些改变链表的操作都会导致这个变量自加1

public void sort(Comparator c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++; //自加1
}

有些操作会定义一个变量,如上面的sort操作,在真正排序数据之前会定义expectedModCount ,然后在排序操作之后再比较两者是否相等,如果不相等就会抛出ConcurrentModificationException。
另外,使用迭代器也会做如上操作,在ArrayList的迭代器中,也有一个expectedModCount 变量,并且在每一次执行next()方法的时候都会检查expectedModCount是否和ModCount相等,因此在使用迭代器遍历的是否,就不能直接使用remove操作了,下面的例子就会报错:

List l1 = new ArrayList<>();
        l1.add(1);
        l1.add(2);
        l1.add(3);
        l1.add(4);
        l1.add(5);
        l1.add(6);

        Iterator it = l1.iterator();
        while (it.hasNext()) {
            Integer integer = it.next();
            if (integer == 3) {
                l1.remove(3);
            }
        }

因为remove操作改变了一次ArrayList导致ModCount加1.
但是使用迭代器本身所带的remove方法是不会报错的

it.remove();

因为迭代器的remove操作对expectedModCount 重新赋了值,所以不会报错。因此问题就是这样解决了。

思考

为什么要使用ModCount这样的设计?
其实对链表遍历时,本来就不应该再进行其他可能会改变链表结构的操作了,因为这样可能会导致遍历有问题,比如说:某些元素可能会遍历两次,有些元素没有被遍历到,这肯定是我们不想看到的,比如下面的例子:

List l1 = new ArrayList<>();
        l1.add(1);
        l1.add(2);
        l1.add(3);
        l1.add(4);
        l1.add(5);
        l1.add(6);
        for (int i = 0; i < l1.size(); i++) {
            Log.d("value", "is--" + l1.get(i) + ";and i-->" + i);
            l1.remove(i);
        }

打印结果是:

D/value: is--1;and i-->0
D/value: is--3;and i-->1
D/value: is--5;and i-->2

上面就有一些元素没有被遍历到,因此最好不要在遍历的时候修改链表,修改的话也就使用迭代器修改当前的位置的元素。这也是为什么大家都说使用迭代器比较安全的原因,因为如果不小心修改到了数据,它立马就会爆出错误,让我们察觉到。
因此尽量在平时遍历的时候,使用下面的形式:

for(A item : itemList){
	// ......
}

上面的形式,java会把它转变成迭代器的形式。

扩展

上面的遍历方式会发现一个问题,就是不能确定当前遍历到第几个数据了,这就有点蛋疼了,总不能每次都使用list查询吧,通过查看源码,我发现List这个数据结构还有一个迭代器:ListIterator,看看这个迭代器支持什么功能:

/**
 * An iterator for lists that allows the programmer
 * to traverse the list in either direction, modify
 * the list during iteration, and obtain the iterator's
 * current position in the list */

支持倒序遍历,获取当前位置还能改变当前的遍历位置!!!哎呀妈的,真香。还有这么好用的东西,相见恨晚啊,以后就使用这个迭代器遍历了,装逼立马提升一个档次。

你可能感兴趣的:(java)