为什么在foreach循环中进行元素remove/add操作,会抛ConcurrentModificationException 异常?

场景

有如下代码运行:

    @Test
    public void testRemove() {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        for (String item : list) {
            // 当为1时运行正常,改成2时异常ConcurrentModificationException
            if ("2".equals(item)) {
                list.remove(item);
            }
        }

        System.out.println(PrintUtils.parintArrayList(list));
    }

异常如下:

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at NormalTest.testRemove(NormalTest.java:98)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    ...

分析

将字节码反编译

反编译结果
上图发现,我们简简单单的一句for (String item : list) { 在真实的字节码中却变成了红框中的部分,进一步分析。

跟踪源码

第一行

    public Iterator<E> iterator() {
        // ArrayList中的一个内部类
        return new Itr();
    }

在创建该对象时有这么一句赋值语句
这里写图片描述
该字段在ArrayList中标识当前对象的修改次数(包括remove和add方法),跟踪代码不难发现都有这么一行代码modCount++;

第二行
查看while条件中的源码,并不会发生异常,暂且不管

        public boolean hasNext() {
            return cursor != size;
        }

第三行
仔细查看Itr代码中的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];
        }

再看checkForComodification 方法,

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

结合上面的判断,在创建Itr对象时就将ArrayList的修改次数modCount赋值给Itr迭代器对象,如果在迭代期间ArrayList对象被操作(remove和add)了导致modCount 值修改,就会报异常ConcurrentModificationException

再分析

那么为什么当判断条件为“1”时 if ("1".equals(item)) { 就不会报错呢?
查看while条件中的源码涉及到两个字段cursorsize,做个表格来一步一步记录下值的变化

public boolean hasNext() {
    return cursor != size;
}

判断条件为if ("1".equals(item)) {

注:因为是两次add,所以导致了expectedModCountmodCount初始值为2

迭代次数cursorsizeexpectedModCountmodCount
新创建0222
第一次next1222
执行remove操作后1123

以上就能看到,当remove操作后就导致了cursorsize一致,也就退出了while循环,避免了异常抛出。

判断条件为if ("2".equals(item)) {

迭代次数cursorsizeexpectedModCountmodCount
新创建0222
第一次next1222
第二次next2222
执行remove操作后2123
while判断条件仍为true,继续执行2123
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

所以在next代码块中的checkForComodification就抛出了异常,以上也就完美了解释了为什么当判断条件为2时抛异常了。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值