并发修改异常浅析

  本篇是对于集合类源码浅析のArrayList中第五部分并发修改异常原因分析的扩展补充。


一、问题描述

  我们首先看一段代码:

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        for (String s : list) {
            if ("1".equals(s)) {
                list.remove(s);
            }
        }
    }

  定义了一个集合,存入了三个元素,然后在增强for循环中对元素进行删除,然后运行,毫无疑问地出现了ConcurrentModificationException(并发修改异常)。这也是经典面试题中所说,不能一边遍历集合一边删除元素,如果必须要这么做,需要使用迭代器

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at com.light.gulimall.product.web.ItemController.main(ItemController.java:41)

  下面我们从源码的角度对问题进行分析


二、问题分析

  增强型 For 循环是 For 循环的语法糖,经过编译后的class文件中,是这样:

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String s = (String)var2.next();
            if ("1".equals(s)) {
                list.remove(s);
            }
        }

    }

  我们跟踪进list.remove(s)这行代码,发现最终每次执行一次remove操作,AbstractList抽象类的成员变量modCount都会+1。
在这里插入图片描述  从modCount的注释上也可以看出,新增,删除等操作,都会导致这个变量的值增加:
在这里插入图片描述  再次观察报错时的堆栈信息,发现问题出现在java.util.ArrayList$Itr.next(ArrayList.java:851)java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901):
  第一个Itr.next,对应的是编译后class文件的
在这里插入图片描述  继续跟进,进入java.util.Iterator接口,选择如图的实现:
在这里插入图片描述  会发现在执行移动指针的的逻辑之前,先进入checkForComodification()方法,也就是先前报错信息中的Itr.checkForComodification
在这里插入图片描述  在checkForComodification()方法中会进行判断,将当前的modCountexpectedModCount进行比较,如果不一致就会抛出最开始的异常。
在这里插入图片描述  而expectedModCount是Itr的一个成员变量,它的值是modCount,并且后续不会对此变量的值进行更新,只在构造Itr对象时进行了初始化。
在这里插入图片描述  每次进行添加元素,删除元素时,modCount都会+1,而expectedModCount不会改变,必然两者不会相等,所以在移动指针时校验不通过,出现经典的ConcurrentModificationException(并发修改异常)。
在这里插入图片描述


三、问题解决

  通过上面的分析,想必已经对为什么会出现并发修改异常,有了一定的认识,那么如何避免这样的问题?答案是可以使用迭代器:

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");

        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String element = it.next();
            it.remove();
        }
         System.out.println(list);
    }

  执行后没有发生异常,并且集合中的元素最终被清空,原因在于,使用了迭代器的remove() 方法,会在调用集合的remove()方法后,将最新的modCount重新赋值给expectedModCount,所以在下一次执行it.next()移动指针时,校验两者是否相等则不会抛出异常。
在这里插入图片描述

四、总结

  集合类中,modCount字段设计的目的就是为了标记修改的次数,从而保证快速失败的机制。子类可以自由选择是否使用这个字段。官方也是不推荐一边遍历集合,一边删除元素的操作。如果一定要,可以使用迭代器避免并发修改异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值