copyonwritearraylist的fail-safe机制是主动选择不读取最新写入,通过创建迭代开始时的数组快照实现线程安全;迭代器基于该不可变副本遍历,不抛concurrentmodificationexception,但无法反映后续写操作,适用于读多写少场景。

CopyOnWriteArrayList 的 fail-safe 机制不是“无法读取最新写入”,而是**主动选择不读取最新写入**——它通过快照(snapshot)实现线程安全,读操作基于迭代开始时的数组副本,因此天然不反映后续的写变更。
快照机制:读操作看到的是“过去某一时刻”的数据
每次调用 iterator() 或增强 for 循环时,CopyOnWriteArrayList 会复制当前内部数组的一个引用(注意:是引用拷贝,不是深拷贝),整个迭代过程都基于这个静态快照进行。即使其他线程在此期间成功添加/删除元素,这些变更只会生成新数组并替换原引用,但已有迭代器仍持有旧数组引用,所以看不到新数据。
- 写操作(add/remove/set)会加锁、创建新数组、复制旧数据、写入变更、最后原子更新内部数组引用
- 读操作(包括 iterator、size()、get())全程无锁,但 get(i) 直接访问当前数组,而 iterator 是基于构造时的数组副本
- 关键点:iterator 的“快照”在创建那一刻就固定了,后续写操作不影响它
fail-safe 不等于实时一致,而是“不抛 ConcurrentModificationException”
fail-safe 的本意是避免迭代过程中因结构修改而抛出异常,不是保证强一致性。它牺牲实时可见性来换取读操作的无锁和安全性:
- 普通 ArrayList 的 iterator 是 fail-fast:检测到 modCount 变化立即抛 CME
- CopyOnWriteArrayList 的 iterator 是 fail-safe:不检查 modCount,也不感知外部修改,自然不会抛异常
- 这种设计适合读多写少、且允许读延迟的场景(如监听器列表、配置缓存)
为什么不能让 iterator 看到最新写入?技术上不可行
若强制 iterator 实时反映写入,需满足两个条件之一,但二者均违背 CopyOnWriteArrayList 的设计原则:
- 让 iterator 持有对最新数组引用的动态追踪 → 需要额外同步或 volatile 读,破坏无锁读优势
- 让写操作通知所有活跃 iterator 切换数组 → 违反 lock-free 设计,引入复杂生命周期管理与内存泄漏风险
- 更本质地,快照的核心价值就在于“隔离”——读写互不干扰,这是以空间换时间、换安全性的明确取舍
如果需要读到最新数据,该怎么做?
这不是 CopyOnWriteArrayList 的使用场景。应根据实际需求切换策略:
- 读写频率接近 → 考虑
ConcurrentHashMap(key 做索引)或ConcurrentLinkedQueue - 需要强一致性读 → 使用
synchronized块 + 普通 ArrayList,或ReentrantReadWriteLock控制 - 仅需最终一致性且写极少 → 保留 CopyOnWriteArrayList,但接受读延迟(例如监听器通知后才生效)
- 想“立即看到新增”,可单独维护一个 volatile 标记或版本号,配合业务逻辑判断是否需重新获取 list
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











