Java的ConcurrentModificationException异常介绍和解决方案

关于ConcurrentModificationException 异常介绍

在一个线程遍历集合的时候(如ArrayList,HashMap),结构被修改(如remove, add),就会抛出这个异常。

是一个fail fast机制,为了在并发修改的时候发现问题,而不是返回错误数据。

出现的原因

源于ArrayList中的modCount字段

protected transient int modCount = 0;

这个字段的作用是记录结构的修改次数

还有Iterator中的expectedModCount字段,如果expectedModCount不等于modCount,就会抛出CME

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;

    public void remove() {
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}
  • modCount:记录集合被结构修改的次数(add、remove、clear等)
  • expectedModCount:迭代器期望的修改次数

在代码中出现CME的情况

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

for (String s : list) {
    list.remove(s); // 抛 ConcurrentModificationException
}

以上代码的链表就会发生结构变化,究其根本就是ArrayList修改了但是没有同步到Iterator迭代器,导致modCount != expectedCount从而抛出CME

正确的写法

1)使用Iterator.remove()方法

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("A")) {
        it.remove();  // ✔ 不会抛 CME
    }
}

原因是:

public void remove() {
    // ...code...
    try {
        ArrayList.this.remove(lastRet); // 调用集合的remove
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount; // 关键:同步更新!
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

2)使用CopyOnWriteArrayList

CopyOnWriteArrayList适合读多写少

List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");

for (String s : list) {
    list.remove(s);  // ✔ 完全没问题
}

因为CopyOnWriteArrayList修改时会创建新的数组,读数据还是遍历旧数组,不会发生CME

3)使用 for 循环倒序遍历

for (int i = list.size() - 1; i >= 0; i--) {
    list.remove(i); // ✔ 不会 CME
}

4)使用removeIf()

list.removeIf(s -> s.equals("A"));

多线程下出现CME的情况

List<Integer> list = new ArrayList<>();

new Thread(() -> {
    list.add(1);
}).start();

new Thread(() -> {
    for (Integer i : list) {
        System.out.println(i); // ❌ 可能 CME
    }
}).start();

解决方案:

使用并发集合:

  1. ConcurrentHashMap
  2. CopyOnWriteArrayList
  3. ConcurrentLinkedQueue
  4. ConcurrentSkipListMap

Fail-fast是什么

指的是程序在运行代码的过程中,如果遇到错误或者异常状态,立即抛出异常停止运行,避免在后续的操作中引发更严重的数据不一致

posted @ 2025-11-23 22:45  Lantz12  阅读(29)  评论(0)    收藏  举报