【Java从入门到放弃 之 ConcurrentModificationException】

发布于:2024-12-18 ⋅ 阅读:(117) ⋅ 点赞:(0)

ConcurrentModificationException

ConcurrentModificationException 是 Java 中的一种运行时异常,通常发生在使用迭代器遍历集合(如 ArrayList, HashSet, HashMap 等)的同时,通过其他方式修改了集合的内容(添加、删除或更新元素)。这种情况下,Java 的集合框架为了保证数据的一致性和安全性,会抛出 ConcurrentModificationException 来阻止潜在的并发修改问题。

我相信很多初学Java的人一定会遇见这异常的。

public class Test30 {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(0);
        list.add(1);
        for (Integer integer : list) {
            if (integer == 1) {
                list.add(2);
            }
        }
    }
}

运行这段代码,会出现:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at com.example.mydemo.test.Test30.main(Test30.java:12)

探索ConcurrentModificationException

ConcurrentModificationException 主要由以下几种情况引起:

  • 在迭代过程中直接修改集合:例如,在使用 Iterator 遍历一个集合时,直接调用集合的方法(如 add(), remove() 或 clear())来修改集合。
  • 多个线程同时访问和修改同一个集合:如果一个线程正在遍历集合,而另一个线程试图修改它,可能会导致此异常。
  • 使用增强的 for 循环(for-each loop)时修改集合:这是第一种情况的一个特例,因为增强的 for 循环内部实际上是使用了迭代器。
        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];
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

我们做foreach循环的时候,返回了Iterator对象,这个对象的modCount是新建立这个对象时候的modCount值。但是后面List.add的时候modCount+1了。导致后面Iterator判定预期修改值与实际修改值不相同。 下面是Itr的源码,我们明显看到expectedModCount是取得新建这个对象的时候modCount的值,导致后面集合修改了modCount值之后,就出现不一致的问题。

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

// 中间删除了一部分代码,有兴趣的同学 可以自己去源码里面去搜索

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

问题点就是checkForComodification的时候发现,预期修改次数与实际修改次数不相同,所以抛出了ConcurrentModificationException。这个解释有点像是反推,并不能让我们了解ConcurrentModificationException这个异常设计的根源问题。
实际上我们发现foreach循环最终转换成的是迭代器循环Iterator进行循环。而我们remove的时候调用的是Collection相关的对象。像上面那个代码。我们修改集合调用的是List对象。但是循环的时候又是采用的Iterator对象循环的。

看到问题点了吗
循环用的Iterator对象;修改集合用的是Collection对象。这就导致了一问题,如果要保证正确性,需要两个对象进行通信。通信就牵扯到了很多问题。最大的问题是什么? 是并发问题,所以为了保证正确性,引入了checkForComodification保证并发的正确性,这也导致了我们需要设计一个异常去处理这个问题。这就是为什么会有ConcurrentModificationException这个异常。

解决问题

上面我们已经清楚的知道了问题点,问题点在于循环用的Iterator对象;修改集合用的是Collection对象通信导致的。其实上面的那段代码,如果不采用集合去清理元素元素,就不会产生这样的错误。

public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

但是Iterator对象实际上只有remove操作,没有新增元素的操作。 所以我们可以在删除元素的时候使用迭代器去删除,这样就更加安全。

总结

ConcurrentModificationException 是由于在遍历集合的同时对其进行修改所引起的常见问题。为了避免这种情况,开发者应根据具体的应用场景选择合适的解决策略,比如使用迭代器的安全方法、采用线程安全的集合类型、实施适当的同步措施或利用现代 Java 特性如流式 API。正确处理这个问题不仅可以提高程序的稳定性,还能改善其性能和可维护性。

** 现在你了解ConcurrentModificationException出现的原因了吗** 欢迎大家评论,留言,私信交流


网站公告

今日签到

点亮在社区的每一天
去签到