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();
}
相关推荐
子兮曰21 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
小羊没烦恼!21 小时前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
子兮曰21 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
俊昭喜喜里21 小时前
java中的继承和多态的区别
java
小羊没烦恼!21 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
爱勇宝21 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
譕痕21 小时前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码1 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖1 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时1 天前
01-06-A-JVM排查实战详解
java·jvm