CopyOnWrite机制通过读写分离与快照一致性保障遍历线程安全:迭代器持数组快照只读,写操作复制新数组并原子替换引用,遍历时看不到新增元素、仍可见已删元素,且不抛ConcurrentModificationException。

CopyOnWrite 机制保障遍历安全,核心在于“读写分离”和“快照一致性”——遍历时不碰原数组,也不受写操作干扰,自然不会出错。
迭代器持有数组快照,遍历与修改互不感知
每次调用 iterator() 或使用增强 for 循环时,CopyOnWriteArrayList/CopyOnWriteArraySet 的迭代器会立即捕获当前底层数组的引用,形成一个只读快照。后续所有遍历操作(next()、hasNext())都基于这个固定数组副本进行,哪怕其他线程此时正在 add/remove,也只会生成新数组并更新内部引用,完全不影响正在运行的遍历逻辑。
这意味着:
- 遍历中看不到本次迭代开始后新增的元素
- 遍历中仍能访问本次迭代开始前已被 remove 的元素(因为快照里还存着)
- 永远不会抛 ConcurrentModificationException
写操作复制新数组,原子替换引用
所有修改操作(add、remove、set)都不直接修改原数组,而是:
- 先创建原数组的完整副本
- 在副本上执行实际变更
- 用 volatile 引用原子更新指向新数组
这个过程对读线程完全透明:旧迭代器继续读老数组,新迭代器读新数组,没有锁、没有阻塞、也没有状态竞争。
遍历时必须避开 size() + get() 混合用法
虽然 CopyOnWrite 容器本身线程安全,但手动用下标遍历容易引入不一致视图:
-
✅ 安全写法:增强 for(
for (E e : list))或显式 iterator -
❌ 危险写法:用
list.size()获取长度,再循环调用list.get(i)—— 因为 size() 和 get(i) 可能读到不同版本的数组(size 返回新长度,get 却读到旧数组越界位置)
CopyOnWriteArraySet 的特殊约束
它底层复用 CopyOnWriteArrayList,但额外保证元素唯一性:
- 添加时调用 addIfAbsent(),避免重复
- 迭代器同样快照化,但不支持 remove()——调用会直接抛 UnsupportedOperationException
- 不能存 null(注意:部分 JDK 版本允许,但官方文档明确不保证,应避免)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











