copyonwritearraylist 遍历时提供不可变数组快照,保证遍历线程安全且不抛 concurrentmodificationexception;快照为读时复制引用、写时换引用,具弱一致性,仅保障单次遍历元素级自洽,不保证实时同步或业务逻辑一致。

CopyOnWriteArrayList 在遍历时提供的是当前时刻的数组快照,这个快照一旦生成就不再变化,因此遍历过程绝对线程安全,不会抛出 ConcurrentModificationException,也不会看到“半更新”状态的数据——但它的一致性是弱一致性:快照只反映某次读取时的瞬时状态,不保证与其它线程操作的实时同步或因果顺序。
快照如何生成:读时复制,写时换引用
每次调用 iterator() 或 listIterator() 时,CopyOnWriteArrayList 会直接复制当前内部数组的引用(不是深拷贝元素,而是浅拷贝数组对象地址),形成一个不可变视图。后续所有遍历操作都基于这个固定数组副本进行。
- 写操作(add/remove/set)会创建新数组,完成修改后原子性地替换原数组引用(通过
volatile修饰的array字段) - 正在遍历的迭代器看不到新数组,因为它持有的是旧引用;新迭代器才会拿到新引用
- 所以多个并发遍历各自看到的可能是不同时间点的快照,彼此之间无顺序保证
快照的一致性边界:元素级可见性,非事务级一致性
快照能保证单次遍历中看到的元素内容是自洽的:比如遍历时不会出现某个元素为 null 而下一个非 null 的“撕裂”现象,因为整个数组引用是原子读取的,且数组本身在遍历期间不会被修改。
- 但不能保证逻辑上的业务一致性。例如:你用 CopyOnWriteArrayList 存储账户余额列表,A 线程遍历时看到 [100, 200],B 线程此时把第一个改成了 50、第二个改成了 250,A 仍按 [100, 200] 处理——这不是 bug,而是设计使然
- 元素本身如果是可变对象(如普通 POJO),快照不阻止其它线程修改其字段值,仅保证数组引用和元素引用不变
适用场景与风险提示
它适合读多写少、允许短暂陈旧视图、无需强实时一致性的场景,比如监听器列表、配置项缓存、状态快照上报等。
- 避免用于需要强一致读写的场景,例如银行转账核对、库存扣减校验
- 注意内存开销:每次写操作都复制整个数组,大数据量时 GC 压力明显
- 迭代器不支持 remove/add 操作(调用会抛
UnsupportedOperationException),因为快照不可变
与其它并发集合的一致性对比
不同于 ConcurrentHashMap 的分段锁或 CAS + 链表/红黑树结构提供的近实时一致性,也不同于 BlockingQueue 类提供的阻塞+顺序保证,CopyOnWriteArrayList 的一致性模型更接近“最终一致下的确定性快照”——每次读都是确定的、隔离的,但不同读之间没有同步协议。
- 它不依赖锁或 CAS 来协调读写,靠的是空间换时间(复制)和引用原子性
- 其“一致性”本质是遍历过程的内部一致性,而非跨操作、跨线程的一致性
- 如果需要跨操作一致,应配合外部同步机制(如 ReentrantLock)或选用更重的事务型结构(如数据库)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











