ArrayList快速失败机制

一、什么是快速失败机制

ArrayList实现了一种称为快速失败(fail-fast)的机制,该机制在并发修改时会抛出ConcurrentModificationException异常。
这种机制的实现原理是:ArrayList在遍历时会记录列表的修改总数(通过modCount字段),如果在遍历过程中列表结构发生变化,那么modCount的值会增大。每次遍历前,迭代器都会检查modCount是否发生改变,如果改变则抛出异常,表示列表并发修改
这个机制的作用是确保在遍历列时候,列表的结构保持稳定,避免并发修改带来未知的结果。

二、例子

        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        for(String s : list){
            if("C".equals(s)){
                list.remove(s);
            }
        }

		System.out.println(list);

在这里插入图片描述
因为在遍历过程中调用 list.remove(s) 修改了列表结构,这会导致modCount的值增加,迭代器在下一步遍历前检查到modCount改变,所以抛出异常。

三、底层原理

AbstractList中定义了modCount并初始化为0,modCount是用来记录修改表结构的次数
在这里插入图片描述

在这里插入图片描述
add操作会将modCount+1
![在这里插入图片描述](https://img-blog.csdnimg.cn/61e2d604564c4d75a9c8237ce7450b12.png
remove方法同样会使modCount++
在这里插入图片描述
看到ArrayList中的迭代器的实现,先将expectModCount的值设为modCount的值,在next()方法中最开始会执行一个checkForComodification()方法
在这里插入图片描述
这个方法的作用就是检查expectedModCount与modCount是否相等,如果在迭代遍历过程中发生了对list结构的修改操作,modCount的值就不会跟expectedModCount相等,然后抛出异常
在这里插入图片描述

二中的例子代码在编译后的结果

        List<String> list = new ArrayList();
        list.add("A");
        list.add("B");
        list.add("C");
        Iterator var2 = list.iterator();

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

        System.out.println(list);

可以看到增强for循环其实本质上就是使用迭代器去遍历

        List<String> list = new ArrayList();
        list.add("A"); // modCount = 1
        list.add("B"); // modCount = 2
        list.add("C"); // modCount = 3
        Iterator var2 = list.iterator(); // expectedModCount = modCount = 3

        while(var2.hasNext()) {
            String s = (String)var2.next(); // 会检查expectedModCount与modCount是否相等
            if ("C".equals(s)) { 
                list.remove(s); // modCount = 4
            }
        }

        System.out.println(list);

这里有人会问,这里明明已经执行到最后一个元素了,会跳出循环,就不会执行next方法,也就不会抛出异常了。看看hasNext()方法的实现
在这里插入图片描述
如果没有删除C元素,此时cursor = size = 3
但是因为remove了一个元素,此时数组大小size变成2,而cursor为3,依旧会进入循环去执行next方法,这次执行就会抛出异常。

四、解决方法

要解决这个问题,我们有几种方式:

  1. 遍历结束后再修改列表结构。这是最简单的方式。
  2. 使用迭代器自己的remove()方法。该方法会判断删除元素是否是迭代器当前元素,如果不是则抛出异常,确保安全。
  3. 使用更高级的并发集合,如CopyOnWriteArrayList。该集合在修改时会返回集合的新副本,确保线程安全。
  4. 使用ListIterator,它支持在遍历过程中安全添加和删除。
  5. 调用removeIf()等在遍历时支持删除的方法。

五、快速失败机制的一个小bug

这里使用的是jdk11

        List<String> list = new ArrayList();
        list.add("A");
        list.add("B");
        list.add("C");
        Iterator var2 = list.iterator();

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

        System.out.println(list);

if (“B”.equals(s)) 当我把C改成B后执行代码的结果
在这里插入图片描述
可以看到神奇的执行成功了,并没有抛出异常。。。
这里算是一个小bug把,ArrayList如果删除的元素刚好是倒数第二个元素时,不会触发快速失败机制
看到编译后的代码执行过程

        List<String> list = new ArrayList();
        list.add("A"); // modCount = 1
        list.add("B"); // modCount = 2
        list.add("C"); // modCount = 3
        Iterator var2 = list.iterator(); // expectedModCount = modCount = 3

        while(var2.hasNext()) {
            String s = (String)var2.next(); // 会检查expectedModCount与modCount是否相等
            if ("B".equals(s)) { 
                list.remove(s); // modCount = 4
            }
        }

        System.out.println(list);

在执行完移除B的操作后,此时数组变成了 [A,C],数组size变成了2,又因为在执行完next方法之后会将迭代器的下标cursor+1指向下一个元素,此时cursor也为2,然后在hasNext方法里判断就会为false,直接跳出了循环(没有执行会抛异常的那个next方法),导致这次代码执行是成功的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值