问题描述
我们在使用iterator
对象遍历集合时,时常会遇到ConcurrentModificationException
这类的错误
例如:
1 | Collection c = new ArrayList(); |
输出为
1 | Exception in thread "main" java.util.ConcurrentModificationException |
这主要是因为,迭代器遍历集合,实际上是对集合的副本进行操作;这期间,如果迭代器发现自己和集合不一样了,就会报ConcurrentModificationException
异常。
原因 – 迭代器的并发检测
我们可以从JDK源码的获知,当程序执行到it.next()
,实际上调用了如下代码:
1 | public E next() { |
对于第一个函数checkForComodification()
,其源码如下:
1 | final void checkForComodification() { |
这个方法主要是通过比较modConut
和expectedModCount
是否相等,来判断迭代器运行期间,原集合是否发生了变化。其中modCount
为ArrayList
的类成员变量,用来记录其变化次数;而expectedModCount
作为迭代器成员变量,则存储了iterator
初始化时记录到的ArrayList
中的modCount
值。两者相比,即可判断集合是否在迭代器操作期间发生了变化。
解决方法
为了解决这个并发修改异常,最直接的方法就是在iterator
运行期间,不要修改原集合。但万一我们业务需要,必须得修改,又该怎么办呢?
既然不允许我们直接修改原集合,那么我们可以考虑通过迭代器去间接操作原集合。通过查询API文档,我们在Iterator
接口中并未发现add()
方法。既然根类Iterator
中没有,我们就到其子接口中去寻找。不出所料,在其Iterator
子接口ListIterator
中,我们发现了add()
方法。首先看一下它的源码:
1 | public void add(E e) { |
从源码中,我们可以看到,虽然仍然存在并发检测函数checkForComodification()
,但add()
在内部为我们完成了集合元素的添加,最最关键的是它还进行了expectedModCount
与modCount
同步操作,这就保证了并发修改异常不会被触发。
终上所述,最开始的问题代码可以修改如下:
1 | //这里将c的引用类型从Collection换成List,是因为Collection接口中没有listIterator()方法,多态无法调用父接口中没有的方法 |