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();
}
相关推荐
源码_V_saaskw1 分钟前
JAVA国际版同城跑腿源码快递代取帮买帮送同城服务源码支持Android+IOS+H5
android·java·ios·微信小程序
TT哇6 分钟前
消息推送机制——WebSocket
java·网络·websocket·网络协议
镜花水月linyi20 分钟前
ConcurrentHashMap 深入解析:从0到1彻底掌握(1.3万字)
java·后端
极客Bob21 分钟前
Java 集合操作完整清单(Java 8+ Stream API)
java
雨中飘荡的记忆21 分钟前
Javassist实战指南
java
uhakadotcom22 分钟前
Loguru 全面教程:常用 API 串联与实战指南
后端·面试·github
Knight_AL29 分钟前
JWT 无状态认证深度解析:原理、优势
java·jwt
JuiceFS33 分钟前
JuiceFS sync 原理解析与性能优化,企业级数据同步利器
运维·后端
寒山李白1 小时前
IDEA中如何配置Java类注释(Java类注释信息配置,如作者、备注、时间等)
java
我要添砖java1 小时前
<JAVAEE> 多线程4-wait和notify方法
android·java·java-ee