ArrayList 并发修改异常?

背景

如下代码

java 复制代码
public static void main(String[] args) {
    ArrayList<String> l = new ArrayList<>();
    l.add("1");
    for (String s : l) {
        l.remove(s);
    }
}

运行代码会报以下错误:

问题:在for循环或forEach中,对ArrayList进行增删操作,会报错ConcurrentModificationException,而倘若使用Iterator进行增删,则不会报错,究竟是何缘故?跟着Arvin来研究一下

原因

根据之前在《代码大全》和《程序员修炼之道:通向务实的最高境界》两书中,均有关于调试的技巧,先冷静,再仔细看报错的内容,其实可以看到这里的堆栈很浅,只有简单的几行调用,那么此时看一下是什么地方抛出的该异常

这里可以看到,,同时在ArrayList中调用iterator()方法时,会返回一个Itr对象,而Itr类实现了迭代器接口,而从上面的堆栈可以看出,是在调用迭代器的next()方法时,方法内部调用checkForComodification()方法时报出的该错误

这个方法里面,会去检查modCount和expectedModCount的值是否一致,modCount顾名思义,就是修改次数,该字段是ArrayList的父类AbstractList的字段

java 复制代码
/**
 * The number of times this list has been <i>structurally modified</i>.
 * Structural modifications are those that change the size of the
 * list, or otherwise perturb it in such a fashion that iterations in
 * progress may yield incorrect results.
 *
 * <p>This field is used by ...
 */
protected transient int modCount = 0;

这个上面说了一大段话都不重要,简单理解就是对里面元素进行增删时,这个参数值会发生变化

那到了这里,就可以看一下在for循环中调用的remove()方法了,

java 复制代码
public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

这里面逻辑不走赘述,就只是判断要删除的元素如果是null,就找到第一个null元素进行删除,否则就找到相同的元素进行删除,之后再执行fastRomve()方法,那再来看这个方法

java 复制代码
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.*arraycopy*(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

这里多的也不需要看,只需关注modCount++这个操作,删除之前,对修改次数进行了变更,而上面next()方法中调用checkForComodification()方法,会去检查modCount和expectedModCount的值是否一致,很明显是完全不一致的,所以就报了这个错

综上,报错的原因,也就找到了

解决方案

  • 如果遍历删除元素,采用迭代器的方式去删除,具体原因,浅析一下迭代器中删除的代码
java 复制代码
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()方法,另外,就是expectedModCount = modCount;操作,这样,再去迭代的时候,就不会报错两个值不一致了

  • 假如只是普通的元素删除,无需遍历,仍然可以使用ArrayList的remove()方法
  • 选用其他的数据结构,例如CopyOnWriteArrayList,但需注意,一定要自己去阅读一下源码,比如这个里面,就不支持在迭代器中删除元素
java 复制代码
/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code remove}
 *         is not supported by this iterator.
 */
public void remove() {
    throw new UnsupportedOperationException();
}
相关推荐
SL_staff1 小时前
物模型三要素误用避坑指南:属性、事件、服务的正确建模逻辑
java
高频因子挖掘机1 小时前
股票分红送转后价格“断层”:前复权、后复权和不复权该怎么选?
后端·github·api
一条泥憨鱼1 小时前
【Redis | 黑马点评】短信登录功能 源码深度分析
java·redis·后端·session·黑马点评
威哥爱编程1 小时前
【AI全栈12-12】Spring Boot 3.x → 4.x 迁移实操:Jakarta 11、Jackson 3 与 AI 2.0
java·spring boot·后端
威哥爱编程1 小时前
【AI全栈12-11】Spring Boot 把 AI 接口真正上线扛量:限流 / 降级 / 可观测
java·spring boot·后端
小蒜学长1 小时前
基于SpringBoot+uni-app的闪洁家政系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·uni-app·家政服务
敲代码的彭于晏1 小时前
2026 K8s 零基础入门:手把手教你一步步搭建 Kubernetes 集群
java·运维·全栈
希洛兔1 小时前
状态模式详解:让对象随内部状态自动改变行为
java·设计模式·状态模式
需要8262 小时前
Spring Boot 启动流程:run() 里到底发生了什么
java·spring boot·后端
云絮.2 小时前
Mybatis(一)
java·数据库·mybatis