copyonwritearraylist迭代器的快照机制是在调用iterator()时获取底层数组的当前引用作为只读静态视图,遍历全程基于该不可变快照,不感知后续写操作,实现无锁、弱一致性、fail-safe遍历。

Java 迭代器的快照机制,核心是“遍历时不看实时数据,只读创建那一刻的副本”。它不是动态跟踪集合变化,而是在调用 iterator() 的瞬间,把当前数据状态固定下来,后续所有 next() 都从这个静态视图里取值。
快照不是深拷贝,而是数组引用复制
以 CopyOnWriteArrayList 为例:它的迭代器在初始化时,直接获取底层 volatile Object[] 的当前引用(比如指向地址 A),并存为自己的 snapshot 字段。这个过程不复制元素内容,也不克隆对象,只是多持有一个不变的数组指针。
- 写操作(如
add)会新建数组、复制旧数据、插入新元素,再用 CAS 原子替换原数组引用 - 正在遍历的迭代器仍指向旧地址 A,完全感知不到新数组的存在
- 元素如果是可变对象(如
new User()),快照不阻止其他线程修改其字段——快照保证的是“哪些元素在、顺序如何”,不是“元素内部状态不变”
快照带来弱一致性,不是强实时
它不承诺你看到的是最新数据,只承诺单次遍历逻辑自洽:不会出现数组长度突变、索引越界、或同一轮遍历中前后元素状态撕裂(比如前一个元素是 null,后一个是有效值)。
- A 线程遍历时看到
[100, 200],B 线程同时把两个值改成[50, 250],A 仍输出原始值——这正常,不是 bug - 多个并发迭代器可能各自看到不同时间点的快照,彼此无先后关系
- 适合监听器列表、配置缓存等场景,不适合库存扣减、账户对账等需强一致读写的逻辑
快照意味着迭代器不可修改
因为快照是只读视图,所以 COWIterator 的 remove()、add()、set() 方法都直接抛 UnsupportedOperationException。
- 想修改集合,必须调用原
CopyOnWriteArrayList的方法(如list.remove(x)) - 但这些修改对已存在的迭代器完全透明,不影响其当前遍历
- 新创建的迭代器才会拿到更新后的数组引用
和 fail-fast 的对比很直观
普通 ArrayList 迭代器依赖 modCount 检查并发修改,一旦检测到结构变更就立刻抛 ConcurrentModificationException;而快照型迭代器压根不检查,它默认接受“数据已经过期”,只要求自己这一趟走得稳。
- fail-fast 是“发现不一致就中断”,强调正确性优先
- fail-safe(快照)是“不中断、不报错、但数据可能陈旧”,强调可用性与无锁读性能
- 两者设计目标不同,不能简单说谁更好,要看场景是否允许读延迟
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











