发生Java ConcurrentModificationException异常深层原因及解决办法

出现该异常场景

ConcurrentModificationException顾名思义就是并发修改异常。
我们先看2段出现异常的代码
第一段代码:迭代器遍历的时候调用list.remove删除元素

	static List<String> list = new ArrayList<>();
	public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            list.add(UUID.randomUUID().toString().substring(0,8));
        }
        printAndRemove();
    }
    public static void printAndRemove() {
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String data = iterator.next();
            System.out.println(data);
            list.remove(data);
        }
    }

第二段代码:多线程情况下往集合添加元素的时候,去输出集合

static List<String> list = new ArrayList<>();
    public static void main(String[] args) throws InterruptedException {
        //  定义5个线程同时对list进行写操作
        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.add(UUID.randomUUID().toString().substring(0,8));
                    System.out.println(list);
                }
            }).start();
        }
    }

以上2段代码都会出现Java ConcurrentModificationException异常,第一段代码异常发生在遍历的时候取下一个元素的时候iterator.next()。第二段代码发生在输出的 时候System.out.println(list),其实直接输出集合,也是调用了list.toString方法,调用了迭代器,发生在iterator.next()这个代码出现异常。

我们可以猜想下出现ConcurrentModificationException异常的时候,

1.在使用迭代器遍历的时候,使用集合的增删改方法对集合进行更新操作。

出现该异常的原因

先看异常信息:

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.fangyajun.javasduty.juc.collection.ArrayListTest.printAndRemove(ArrayListTest.java:31)
	at com.fangyajun.javasduty.juc.collection.ArrayListTest.main(ArrayListTest.java:26)

看异常信息,找出出现异常的源码(指的JDK源码)位置,自下而上看源码信息,范围由大到小,
最后出现异常的代码是:

	final void checkForComodification() {
          if (modCount != expectedModCount)
              throw new ConcurrentModificationException();
     }
  1. 查看源码得知,当modCount != expectedModCount成立的时候就会出现该异常,

  2. 下面我们分析下什么情况下会出现modCount != expectedModCount,我们先来了解下modCount 和 expectedModCount的含义,查看源码得知:

    modCount:是AbstractList的成员变量,ArrayList继承了AbstractList,所以modCount是ArrayLisl的成员变量。
    expectedModCount:是ArrayList内部类Itr的成员变量(集合别地方也定义了局部变量expectedModCount)

    • 查阅源码得知,在对集合进行add,remove,修改的时候都会对modCount+1操作,所以modCount就是对集合更新次数的一个记录
    • 而expectedModCount是在迭代器进行初始化的时候使final int expectedModCount = modCount;,所以,当我们在调用迭代器遍历的过程中有对使modCount+1操作发生,都会使modCount != expectedModCount成立
    • 通过分析得知,可以很容易得出,一个集合在遍历的过程中,如果对集合进行新值,新增,修改,删除操作都会发生ConcurrentModificationException异常

综合以上分析:

  • 当集合在进行一个操作(比如遍历,排序)的过程中,我们对集合进行了更新(新值,修改,删除都是更新)操作,那么就会发生ConcurrentModificationException异常。

  • 有的文章会带你分析迭代器的源码,说迭代器各种指针变动,其实出现该异常,和迭代器本身没有关系,迭代器的作用就是遍历,主要是迭代器定义了expectedModCount变量,只要在迭代的过程中,引发了modCount+1操作的发生,就会发生异常。

  • 除了迭代器遍历操作,还有对集合排序,对集合元素替换等操作的过程中只要发了modCount != expectedModCount都会发生异常,可以看下面的ArrayList源代码就知道(不作分析,可以秒懂):

     @Override
        @SuppressWarnings("unchecked")
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            final int expectedModCount = modCount;
            final int size = this.size;
            for (int i=0; modCount == expectedModCount && i < size; i++) {
                elementData[i] = operator.apply((E) elementData[i]);
            }
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public void sort(Comparator<? super E> c) {
            final int expectedModCount = modCount;
            Arrays.sort((E[]) elementData, 0, size, c);
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }
    

    解决该异常的方法

    通过分析我们知道原因了,主要是在操作集合的过程中,我们对就行了更新操作,那么我们如何来防止该异常的发生呢?

    各个场景不一样,解决办法也不一样:

  • 如果是使用迭代器遍历的时候,删除元素,那么我们不要使用集合本身的remove方法进行删除,而是使用迭代器的方法进行删除代码如下:

    public static void printAndRemove() {
            Iterator<String> iterator = list.iterator();
            while (iterator.hasNext()) {
                String data = iterator.next();
                System.out.println(data);
                iterator.remove();
            }
        }
    

    为什么使用迭代器的remove()方法不会发生异常呢,是因为迭代器的删除不会引发modCount+1操作,他是迭代器内部的操作,删除的是已经迭代器上一个已经遍历的元素。

  • 如果是多线程先下发生该异常,我们可以使用并发安全类(CopyOnWriteArrayList等)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值