思考分析

在使用list集合时,如果我们使用迭代器进行遍历,同时在遍历过程中尝试添加元素,运行后会爆出这样的错误

 源码是这样的

 按理说没有什么问题,为什么会报错?复制报错信息到API文档中

当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常。

某些迭代器实现(包括由JRE提供的所有通用集合实现的实现)可能会选择在检测到此行为时抛出此异常

某些迭代器实现:迭代器实现,迭代器是个接口,

再看错误信息

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)

Arraylist中的某个checkForComodification方法

Arryaylist中的next方法

ctrl+b进入源码

at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)点击(ArrayList.java:909)

我们会看到这样的一串代码,可以看到这个if判断

modCount != expectedModCount,某个变量和这个变量相等抛出异常ConcurrentModificationException
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

这个变量是什么?我们现在好不清楚

先看看这个at java.util.ArrayList$Itr.next(ArrayList.java:859)点击ArrayList.java:859

public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

似乎也不是很懂,但是我们能直到这是next的构造方法,我们仔细看会有个东西是这个

checkForComodification()

和上面的正好对应上了,说明上面是个方法,也就是上面的运行错误影响了下面的错误,一环套一环

回去我们看上面的

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

在中间我们看到了中间的判断

if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

modCount != expectedModCount,百度是这样回答的 modCount字面意思就是修改次数

expectedModCount

expectedModCount则是表示迭代器对集合进行修改的次数

也就是说这两个次数不相等就会抛出throw异常

那这个modCount为什么会不相等,我们在源码中寻找modCount,选中modCount按CTRL+B

protected transient int modCount = 0;    

int modCount = 0;  初始化次数为0

意思就是说修改次数为0

同样我们选中expectedModCount按CTRL+B

private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

 看到这一行,修改次数和迭代器对集合进行修改的次数是相同的,那为什么相等了还是出现异常,说明这两个次数肯定有个发生了变化

int expectedModCount = modCount

 到这我们也不知到从哪里分析了,回去看代码,看最后一行

 

 

 显示我们代码第19行有问题

 这个next方法是迭代器的,那我们就找到这个next的构造方法

public E next() {
            checkForComodification();
            int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }

 似乎遇到了瓶颈,上面的东西都对着,别急,我们接着看代码第19下面的添加

list.add("javaee");

 这里添加就是修改,会不会问题出自这里呢?

先找到这个方法CTRL+B

boolean add(E e);

看着很懵逼,是个抽象方法,拉到开头

public interface Collection<E> extends Iterable<E> {}

extends继承,Collection,我们创建的list,他也是Collection的子类,这里找错了,我们因该找list

boolean add(E e);

list集合也是这样的,在拉到开头还是collection

陷入死循环了,别急,再想想,

List<String> list = new ArrayList<String>();

我们是通过Arraylist集合,向上转型,获取到子类的变量

那有没有可能是,父类调用子类的方法

那我们依照这个思路打开选中Arraylist 按CTRL+B

找到add构造方法

果然在Arraylist有这个方法

 public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

add添加,modCount++,

modCount++;

modCount++,修改次数增加,原因也就很清晰了,这里修改次数增加了,不相等了,也就抛出异常了

在这个过程中有可能会找不到这个,没有modCount++

类似于这个

 

 你也别急,看着这一串英文也是头疼哈,你看看它里面有个

ensureCapacityInternal(size + 1); 

 看样子像是个方法,我们点击它,按CTRL+B,

 

 这里还有方法,我不服,我继续打开

 

 忽然眼前清晰了许多,看到了熟悉的东西,也即是说,这个add方法中调用好多方法,将modCount++给封到最后一层

分析结果如上,因为modCount++,修改次数增加,与迭代器对集合进行修改的次数不相等了抛出了异常。

简单来讲就是

  迭代器遍历的过程中,通过集合对象修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际修改值不一致

如何解决这个异常使用for循环

有兴趣你也可以研究一下,底层的源码

这里给出get的构造方法

public E get(int index) {
        Objects.checkIndex(index, size);
        return elementData(index);
    }

和修改后的代码

 

 总结:

  遇到并发修改异常的问题,就是在使用迭代器的next方***有有个判断(迭代器修改次数与修改次数),而迭代器的方法后修改的某值,在使用add方***造成修改次数增加,判(迭代器修改次数与修改次数)不相等,抛出异常。使用for循环,从集合中取出元素,在添加,修改次数不会变化(迭代器修改次数与修改次数相等),不会抛出异常。