ConcurrentModificationException 是 fail-fast 检测机制,用于单线程中发现意外修改,不提供并发安全、不可靠且与弱一致性目标相悖;真正弱一致需用不可变快照、分段视图、无锁协议等可控方案。

不能利用 ConcurrentModificationException 来“设计”弱一致性遍历容器——它是个异常,不是机制。它的作用是**失败快照(fail-fast)检测**,用于在单线程迭代中发现意外的结构修改,而非支持并发或实现弱一致性。
为什么不能靠 ConcurrentModificationException 实现弱一致性
ConcurrentModificationException 是 Java 集合(如 ArrayList、HashMap)在迭代器内部通过 modCount 检查发现“被其他线程或同一线程非迭代器方式修改”时抛出的。它:
- 不提供任何并发安全保证,也不控制读写行为
- 抛出即中断遍历,无法恢复或降级为弱一致结果
- 在多线程下不可靠:可能漏检(因未同步的
modCount读写)、也可能误报(JVM 重排序或 long 拆写问题) - 与弱一致性目标相悖:弱一致性允许遍历看到部分更新、重复或遗漏元素,但要求不崩溃、不抛异常、行为可预期
真正支持并发弱一致性遍历的常用思路
要实现一个自定义容器,支持多线程写入 + 多线程遍历(允许弱一致),应绕过 modCount 和 CME,改用以下更可控的方式:
-
不可变快照(Immutable Snapshot):每次遍历时生成当前数据的不可变副本(如
copy-on-read或copy-on-write)。读不阻塞写,写只影响后续遍历。适合读多写少场景(如CopyOnWriteArrayList) -
分段/版本化视图(Segmented or Versioned View):将容器划分为多个独立段(segment),每段维护自己的版本号;遍历时按开始时刻各段版本取快照。避免全局锁,降低写冲突(类似
ConcurrentHashMap的分段思想) -
无锁迭代协议(Lock-Free Iteration Protocol):使用原子引用(
AtomicReference)指向主数据结构(如链表头或数组),遍历时先读取当前引用,再遍历其指向的数据。写操作原子更新引用(如 CAS 替换整个数组)。遍历看到的是某一历史瞬间的完整结构,不保证最新,但不会 CME 或 NPE -
容忍性迭代器(Tolerant Iterator):不检查修改,也不依赖
modCount;遍历时对元素做 volatile 读或使用Unsafe的有序加载;允许跳过已删除节点、重复访问刚插入节点。需配合线程安全的增删逻辑(如基于 CAS 的链表)
一个极简示例:基于原子引用的弱一致性列表
下面是一个仅支持尾部追加、遍历返回某次读取时刻快照的容器(无 CME,天然弱一致):
public class WeakConsistentList<E> {
private static class Node<E> {
final E item;
final Node<E> next;
Node(E item, Node<E> next) {
this.item = item;
this.next = next;
}
}
private final AtomicReference<Node<E>> head = new AtomicReference<>(null);
public void add(E item) {
Node<E> newNode = new Node<>(item, null);
while (true) {
Node<E> curHead = head.get();
newNode.next = curHead;
if (head.compareAndSet(curHead, newNode)) break;
}
}
// 返回「某一时刻」的快照迭代器,不抛 ConcurrentModificationException
public Iterable<E> snapshot() {
return () -> new Iterator<E>() {
Node<E> current = head.get(); // 一次性读取起始点
public boolean hasNext() { return current != null; }
public E next() {
E item = current.item;
current = current.next;
return item;
}
};
}
}
这个 snapshot() 返回的迭代器只会看到调用时刻已存在的节点(可能遗漏之后添加的,也可能因链表构造顺序看到部分新节点),但绝不会抛 CME,也不会因并发修改而崩溃——这正是弱一致遍历的本质:可用、稳定、有明确定义的行为边界。
总结关键点
-
ConcurrentModificationException是调试辅助工具,不是并发控制原语 - 弱一致性 ≠ 不一致或错误,而是明确接受“过期视图”,并保证遍历过程安全、可终止
- 真正方案聚焦于:快照时机控制、数据结构不可变性/原子可见性、迭代协议解耦
- 是否需要弱一致,取决于业务容忍度;若需强一致遍历(如银行对账),应加锁或用事务内存模型,而非妥协于 CME

















