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()方法时报出的该错误

这个方法里面,会去检查modCountexpectedModCount的值是否一致,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()方法,会去检查modCountexpectedModCount的值是否一致,很明显是完全不一致的,所以就报了这个错

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

解决方案

  • 如果遍历删除元素,采用迭代器的方式去删除,具体原因,浅析一下迭代器中删除的代码
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();
    }
}

这段代码也很简单,只需关注两个点,一个是在这里面仍然去调用了ArrayListremove()方法,另外,就是expectedModCount = modCount;操作,这样,再去迭代的时候,就不会报错两个值不一致了

  • 假如只是普通的元素删除,无需遍历,仍然可以使用ArrayListremove()方法
  • 选用其他的数据结构,例如CopyOnWriteArrayList,但需注意,一定要自己去阅读一下源码,比如这个里面,就不支持在迭代器中删除元素
java 复制代码
/**
 * Not supported. Always throws UnsupportedOperationException.
 * @throws UnsupportedOperationException always; {@code remove}
 *         is not supported by this iterator.
 */
public void remove() {
    throw new UnsupportedOperationException();
}
相关推荐
hashiqimiya2 分钟前
spring框架springbean依赖及单一项目
java·前端·spring
sinat_255487819 分钟前
FileReader/FileWriter
java·开发语言·jvm
历程里程碑13 分钟前
37 线程安全单例模式深度解析
java·服务器·开发语言·前端·javascript·c++·排序算法
柒.梧.14 分钟前
深入理解 HashMap 扩容流程:从 1.7 到 1.8 的演进与细节解析
java
皙然16 分钟前
深入解析 Java 中的 final 关键字
java·开发语言·算法
阿里嘎多学长20 分钟前
2026-03-15 GitHub 热点项目精选
开发语言·程序员·github·代码托管
东离与糖宝22 分钟前
AI IDE冲击下,Java老项目如何平滑迁移到Cursor/AI编程工作流(完整迁移方案)
java·人工智能
刺客xs22 分钟前
C++ 11新特性
java·开发语言·c++
SuperherRo22 分钟前
JAVA攻防-Agent技术&JVM字节码&Premain启动加载&Agentmain运行附加&内存马应用
java·jvm·agent·内存马
雨夜之寂23 分钟前
能动手才推 · AI · 03/15
后端