【Java踩坑】01ArrayList中的remove *** 作导致的ConcurrentModificationException报错 源码解析

【Java踩坑】01ArrayList中的remove *** 作导致的ConcurrentModificationException报错 源码解析,第1张

前要:在看一道题,看到一题ArrayList的remove,问如下是否会报错

	public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        String reString = "张三";
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("小明");
         for (String n : list) {
              if (reString.equals(n)) {
                  list.remove(n);
              }
         }
      }

运行代码,报错如下:

我看了别人的文章,发现别人只是说modCount发生改变,但是具体是如何改变的就没有细说,感觉不够细致。

思考过程:
之前我看过解决的方案使用Iterator迭代器进行遍历。

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        String reString = "张三";
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("小明");
//        报错:ConcurrentModificationException
//        for (String n : list) {
//            if (reString.equals(n)) {
//                list.remove(n);
//            }
//        }
        //iterator 正确写法
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            if (iterator.next().equals(reString)) {
                iterator.remove();
            }
        }
        for (String n:list) {
            System.out.println(n);
        }
    }

执行结果:执行结果和预期结果一致,正确

思考:想着都是使用了remove,有什么不同么?

抱着这样的思考打断点,查看ArrayList源码

首先看ForEach增强循环中的remove源码,如下:

ArrayList源码


此时modCount因为增加了remove *** 作,所以加一,变成了5

接下来,就是报错的关键了,因为ForEach语法糖中的next了,如下



因为modCount(修改次数:5次)和expectedModCount(预计修改的次数:4次)不相等,因为多了一次remove *** 作,所以直接报错。

但我看到这里,我在想,难道使用iterator迭代器遍历remove难道有什么不同么?
果然,在我一次次打断点查看后,发现关键因素是Iterator迭代器本身就是一个内部类,这里的remove是使用的内部类中到方法

Iterator源码:

Fast-Fail机制

通过这个问题,我就衍生出了Fast-Fail机制,其实该机制就是但线程a为了对非线程安全的数据进行遍历时,线程b对数据进行修改,则modCount发生改变,线程a再下一次遍历时会通过checkForComodification()方法检测,判断modCount更改,从而直接报错。

解决方案:
面对并发问题,并发Java中的Copy-On-Write容器可以很好的解决该问题

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

大佬文章:
Fast-Fail机制

收获与总结:

  1. forEach语法糖是使用了hasNext()、next()方法
  2. 对于ArrayList进行数据的修改,需要直接使用Iterator迭代器进行修改
  3. 面对Fast-Fail机制的并发问题,可以使用CoryOnWrite容器,扩展CopyOnWrite特性

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/786674.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存