java.util.ConcurrentModificationException

一、ConcurrentModificationException

import java.util.ArrayList;
import java.util.List;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
        for (String str : list) {
            if ("lisi".equals(str)) {
                list.remove(str);
            }
        }
    }
}

执行结果:

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 com.hq.iteratorTest.IteratorTest.main(IteratorDemo.java:12)

看下 remove(obj) 的源码(以ArrayList为例):

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
}

源码中并没有看到有抛出ConcurrentModificationException的代码,可是为什么会抛出此异常呢?其实编译器在看到一个实现了 Interator 接口的对象,当该集合对象在使用增强 for 循环时,会自动地重写,变成使用迭代器来遍历集合。所以开头的代码,相当于以下代码:

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
        Iterator it = list.iterator();
        while (it.hasNext()){
            String s = it.next();
            if ("lisi".equals(s)){
                list.remove(s); //注意这里调用的是集合的方法
            }
        }
    }
}

虽然使用了迭代器进行遍历,但执行的 remove() 还是集合对象来操作。通常会使用迭代器的 remove() 对集合元素进行操作,这是为什么?首先来看下迭代器中的 remove() 源码:

 public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                ArrayList.this.remove(lastRet);
                // 游标
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
 }

ArrayList 自带的 remove() 源码:

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

异常检测源码:

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

由以上三段代码和对比前面集合的 remove() 可得:

  • modCount 修改次数
  • expectedModCount 期望修改次数

在集合中进行操作时,当modCount != expectedModCount时会抛出修改异常。通过源码可以知道,集合在增加、删除元素时都会修改 modCount 的值;当在集合中删除时,modCount+1,而 expectedModCount 未改变,而在集合删除完之后,迭代器指向下一个对象(即调用next()),会检测出不一致而抛出异常。迭代器 next() 源码如下:

public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
}

迭代器的 remove 方法与集合的 remove 方法最大的不同是,迭代器的 remove 方法中包括对游标和 expectedModCount 的修正。因为 Iterator 是在一个独立的线程中工作的,它在 new Itr() 进行初始化时,会记录当时集合中的元素,可以理解为记录了集合的状态,在使用集合的 remove 方法对集合进行修改时,被记录的集合状态并不会与之同步改变,所以在 cursor 指向下一个要返回的元素时,可能会发生找不到的错误,即抛出ConcurrentModificationException。

很明显,如果使用迭代器提供的 remove 方法时,会对 cursor 进行修正,故不会出现错误。此外,还会修正 expectedModCount,通过它来进行错误检测(迭代过程中,不允许集合的add、remove、clear等改变集合结构的操作)。

二、单线程下解决方法

既然知道了出现异常的关键为 modCount 和 expectedModCount 的值,该如何解决该问题呢?

上文中已经提到的,迭代器的 remove 方法中,有一行代码expectedModCount = modCount;可以保证在修改之后两个变量的值相等。

所以,将之前的代码更正为下面的代码:

 public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
         while (it.hasNext()){
            String s = it.next();
            if ("lisi".equals(s)){
                it.remove(); // 注意这里
            }
        }
    }

三、多线程下解决方法

上面已经提供了解决的方案,但是就适用于所有情况了吗?先看以下一段代码:

public class IteratorTest  {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                Iterator<String> iterator = list.iterator();
                while(iterator.hasNext()){
                    String str = iterator.next();
                    System.out.println(str);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                Iterator<String> iterator = list.iterator();
                while(iterator.hasNext()){
                    String str = iterator.next();
                    if (str.equals("lisi")) {
                        iterator.remove();
                    }
                }
            };
        };
        thread1.start();
        thread2.start();
    }
}

同样的即使使用的是迭代器中的 remove 方法,在多线程情况下,依旧可能会出现异常?

来分析一下出现异常的原因:
当线程 A 执行遍历的第一次时,正常的打印出集合元素,线程 B 也正常的执行。因为无法控制 CPU 的调度,所以运用线程等待的方式,让第二个线程稍快于第一个线程,以检测出异常。当线程 A 等待的时候,线程 B 调用 remove 方法,此时 modCount 值已经自增,而未执行到 expectedModCount = modCount 的代码,此时 expectedModCount != modCount,这个时候线程 A 等待结束,进行第二次循环,当执行 String str = iterator.next(); 时,会进行异常检测,此时因为 expectedModCount != modCount 而抛出异常。

有什么好的解决办法呢?

有人说继承了 AbstractList 的有 ArrayList 和 Vector,ArrayList 是非线程安全的,而 Vector 是线程安全的,可以使用 Vector 来使得在多线程下操作集合不会产生异常。其实这里使用 Vector 依然会出现问题。

public class IteratorTest {
    public static void main(String[] args) {
        Vector<String> list = new Vector<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                Iterator<String> iterator = list.iterator();
                while(iterator.hasNext()){
                    String str = iterator.next();
                    System.out.println(str);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                Iterator<String> iterator = list.iterator();
                while(iterator.hasNext()){
                    String str = iterator.next();
                    if (str.equals("lisi")) {
                        iterator.remove();
                    }
                }
            };
        };
        thread1.start();
        thread2.start();
    }
}

通过例子可以知道,其实 Vector 算不上是一个线程安全的集合类。至于为什么说是线程安全的,可能是因为 Vector 很多方法上采用了 synchronized 的同步关键字,但是请注意:同步 != 线程安全

当使用 Vector 时,虽然在对集合的操作上同步,但仍然使用的是集合中的迭代器,也就是上文说的,当在使用循环的时候使用,只要这个集合包含了迭代器类,那么就会使用迭代器进行循环,所以每个线程的迭代器还是线程私有的,而 modCount 是共享的,这里同样会出现 modCount != expectedModCount 的情况,所以会产生异常情况。

解决方法:
1)在使用 iterator 迭代的时候使用 synchronized 或者 Lock 进行同步;
2)使用并发容器 CopyOnWriteArrayList 代替 ArrayList 和 Vector。

Java.util.ConcurrentModificationExceptionJava编程语言中的一种异常,它通常在并发修改集合抛出。当使用iterator.hasNext()操作迭代器,如果在迭代过程中集合发生了改变(如插入或删除元素),就会抛出该异常。该异常的出现原因是由于集合在迭代过程中被修改,导致迭代器检测到并抛出异常。 解决Java.util.ConcurrentModificationException的方法可以通过以下几种途径: 1. 使用Iterator的remove()方法来删除元素,而不是使用集合自身的remove()方法。这是因为Iterator的remove()方法是安全的,可以避免并发修改异常的发生。 2. 使用synchronized关键字或其他同步机制来保证对集合的访问是线程安全的,避免在迭代过程中发生并发修改。 3. 使用并发集合类,如ConcurrentHashMap或CopyOnWriteArrayList,这些类内部使用了一些机制来保证并发修改的安全性,从而避免了ConcurrentModificationException的发生。 总之,要避免Java.util.ConcurrentModificationException异常的发生,需要注意在迭代集合不要在迭代过程中对集合进行修改,并采取适当的同步机制或使用线程安全的集合类来确保并发修改的安全性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [java.util.ConcurrentModificationException 解决方法](https://download.csdn.net/download/weixin_38723192/14093201)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [java.util.ConcurrentModificationException: null 报错解决](https://blog.csdn.net/qq_51741039/article/details/126008727)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JFS_Study

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

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

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

打赏作者

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

抵扣说明:

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

余额充值