Java ConcurrentModificationException异常原因和解决方法

本文深入探讨了Java中的`ConcurrentModificationException`异常,详细解释了出现该异常的原因,特别是在单线程和多线程环境下的不同情况。提供了在单线程环境下通过修改迭代器的remove()方法避免异常的解决方法,并指出在多线程环境下,即使使用Vector也会遇到类似问题,因为每个线程的iterator有不同的expectedModCount。文章推荐了两种解决办法,包括使用同步机制或采用并发容器CopyOnWriteArrayList。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


参考资料

1 ConcurrentModificationException异常 出现的原因
public class Test {
   public static void main(String[] args)  {
      ArrayList<Integer> list = new ArrayList<Integer>();
      list.add(2);
      Iterator<Integer> iterator = list.iterator();
      while(iterator.hasNext()){
         Integer integer = iterator.next();
         if(integer==2)
            list.remove(integer);
      }
   }
}

运行结果:

Exception in thread "main" java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
 at java.util.ArrayList$Itr.next(ArrayList.java:859)
 at Polymorphic.Test.main(Test.java:13)

expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount

modCount 是AbstractList类中的一个成员变量

protected transient int modCount = 0;

该值表示对List的修改次数,查看ArrayList的add()和remove()方法就可以发现,每次调用add()方法或者remove()方法就会对modCount进行加1操作。

  • next() 方法

在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。

  • remove() 方法

remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。

注意此时各个变量的值:对于iterator,其expectedModCount为0,cursor的值为1,lastRet的值为0
对于list,其modCount为1,size为0。

总结:关键在于
调用list.remove()方法导致modCount和expectedModCount的值不一致
像使用for-each进行迭代实际上也会出现这种问题

2 在单线程环境下的解决办法

在Itr类中也给出了一个remove()方法,在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作

expectedModCount = modCount;

因此,在迭代器中如果要删除元素的话,需要调用Itr类的remove方法
将上述代码改为下面这样就不会报错了:

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                iterator.remove(integer);  //注意这个地方
        }
    }
}
3 在多线程环境下的解决方法

上面在单线程环境下的解决方法,在多线程环境下适用吗?

public class Test {
    static ArrayList<Integer> list = new ArrayList<Integer>();

    public static void main(String[] args)  {
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        Thread thread1 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    System.out.println(integer);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread thread2 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    if(integer==2)
                        iterator.remove();
                }
            }
        };
        thread1.start();
        thread2.start();
    }
}

运行结果:

1
Exception in thread "Thread-0" java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
 at java.util.ArrayList$Itr.next(ArrayList.java:859)
 at Polymorphic.Test$1.run(Test.java:21)

有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误
原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了

一般有 2种解决办法:

  • 在使用iterator迭代的时候使用synchronized或者Lock进行同步
  • 使用并发容器CopyOnWriteArrayList代替ArrayList和Vector
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值