一文搞懂java.util.ConcurrentModificationException异常原因和解决方法

场景复现

对于list,map都有可能出现,这里主要分析list!

    @Test
    public void testList() {
        List<String> list = new ArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        for (String item : list) {
            if (item.equals("1")) {
                System.out.println(item);
                list.remove(item);
            }

        }
        System.out.println(list.size());
    }
    
    @Test
    public void testMap() {
        HashMap<String, Integer> temp = new HashMap<>();
        temp.put("aa", 123);
        temp.put("bb", 123);
        temp.put("cc", 123);
        temp.put("dd", 123);
        for (String s : temp.keySet()) {
            if (s.equals("aa")) {
                temp.put("abcd", 123);
            }
        }

    }
    

原因分析

基本上所有的集合类都会有一个叫做快速失败的校验机制,当一个集合在被多个线程修改并访问时,就会出现ConcurrentModificationException 校验机制。它的实现原理就是我们经常提到的modCount修改计数器。如果在读列表时,modCount发生变化则会抛出ConcurrentModificationException异常。 这与线程同步是两码事,线程同步是为了保护集合中的数据不被脏读、脏写而设置的。

首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常

其实,每次foreach迭代的时候都有两部操作:

  1. iterator.hasNext() //判断是否有下个元素
  2. item = iterator.next() //下个元素是什么,并赋值给上面例子中的item变量

hasNext()方法的代码如下:

public E next(){
        checkForComodification();
        try{
            E next=get(cursor);
            lastRet=cursor++;
            retun next;
        }catch(IndexOutOfBoundsException e){
            checkForComodification();
            throw new NoSuchElementException();
        }
}



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

这时候你会发现这个异常是在next方法的checkForComodification中抛出的,抛出原因是modCount != expectedModCount

  • modCount是指这个list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会自动增减;
  • expectedModCount是指Iterator现在期望这个list被修改的次数是多少次。

iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。

总结:

只有当两个变量值相等时才不会报错。而 list.add()操作和 list.remove(); 操作会使 modCount++; 但是变量 expectedModCount 是内部类的 Itr 子类 的 变量,该子类的实例化方法是:list.iterator(); 实例对象时赋初始值:int expectedModCount = modCount;

所以,不允许在 while(iterator.hasNext()) 循环方法内 有list.add()操作和 list.remove();操作,否则会导致expectedModCount != modCount ,进而导致 throw new ConcurrentModificationException();

解决方法

List

	  //解决1:使用for i形式不用foreach形式
       for (int i = 0; i < list.size(); i++) {
            if (list..equals("1")) {
                list.remove(i);
            }
        }
        System.out.println(list.toString());
        
      //解决2:迭代器形式,并且remove要采用迭代器的
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            Student next = iterator.next();
            if (next.equals("2")) {
                //在这个方法中,iterator.remove()方法,但是它多了一个操作:
                //expectedModCount = modCount;
                iterator.remove();
                //list.remove(next);
            }
        }
        System.out.println(list.toString());

        //解决3:使用并发容器
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

注意上面第二个解决方式:

iterator.remove();在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作:expectedModCount = modCount;

iterator.remove();
list.remove(next);

iterator.remove()源码如下:

public void remove() {
    if (lastRet == -1)
    throw new IllegalStateException();
       checkForComodification();
 
    try {
    AbstractList.this.remove(lastRet);
    if (lastRet < cursor)
        cursor--;
    lastRet = -1;
    expectedModCount = modCount;
    } catch (IndexOutOfBoundsException e) {
    throw new ConcurrentModificationException();
    }
}

多线程情况采用如下:

1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;

2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

Map

//解决方案:使用并发容器即可:
ConcurrentHashMap<String, Integer> temp = new ConcurrentHashMap<>();

参考文章:

https://www.cnblogs.com/yaohuiqin/p/9355874.html

https://blog.csdn.net/androidboy365/article/details/50540202/

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Apple_Web

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值