CopyOnWriteArrayList 采用写时复制机制:写操作加锁、复制数组、修改后更新 volatile 引用;读操作无锁,基于快照,不抛 ConcurrentModificationException;适用于读多写少、可接受弱一致性的场景。

CopyOnWriteArrayList 的核心是“写时复制”——每次修改(add、remove、set)都新建数组,读操作则全程无锁。它适合读多写少、对实时一致性要求不高的场景,但写操作开销大、内存占用高,不适合高频更新或强一致性需求。
写时复制是怎么工作的?
CopyOnWriteArrayList 内部持有一个 volatile 数组引用。所有写操作(如 add)会:
- 先加锁(ReentrantLock),确保同一时刻只有一个线程在写
- 复制当前数组,长度+1(或按需调整)
- 在新数组上完成修改(如插入元素)
- 用 CAS 或 volatile 写将数组引用指向新数组
读操作(get、iterator、forEach)完全不加锁,直接访问当前数组引用所指向的数组——这意味着读到的可能是“旧快照”,无法感知其他线程刚刚完成的写操作。
为什么迭代器不会抛 ConcurrentModificationException?
因为它的 Iterator 是基于创建时的数组快照构造的:
- 调用 iterator() 时,会拷贝当前数组的引用(不是深拷贝元素,但数组对象已不可变)
- 后续 next()、hasNext() 都在这个固定数组上遍历,不受后续 add/remove 影响
- 即使其他线程已替换底层数组,当前迭代器仍安全运行,也不会触发 fail-fast 机制
这是它与 ArrayList 迭代器的本质区别:不是“容忍并发修改”,而是“天然隔离修改”。
哪些场景真正适合用它?
关键看三个条件:读远多于写、能接受短暂数据延迟、不依赖写后立即可见。
- 监听器/观察者列表:如事件总线中注册的回调函数,读(遍历通知)极频繁,写(增删监听器)极少且允许稍迟生效
- 配置项白名单、静态规则集合:比如 IP 黑名单、服务路由节点列表,更新以分钟/小时为单位,查询每毫秒多次
- 监控指标快照采集:如定期 dump 当前活跃连接列表供统计,写仅发生在连接建立/关闭,读用于生成报表
反之,若需要实时反映最新状态(如库存数量)、写操作密集(如高频消息入队)、或内存敏感(大量元素频繁变更),就不适合。
要注意的隐含代价
表面是“读不加锁很轻量”,实则隐藏开销明显:
- 写操作内存放大:每次 add 都新建数组,原数组等待 GC;10 万元素 add 一次 ≈ 多分配 10 万 × 元素大小内存
- 写吞吐低:ReentrantLock 是独占锁,写线程串行化,高并发写反而比 synchronized ArrayList 更慢
- 弱一致性边界模糊:get(index) 可能返回旧值,size() 返回的是快照长度,但新写入元素尚未被包含——不能用于判断“是否已添加成功”
它不是万能的“并发安全 ArrayList 替代品”,而是一个有明确取舍的设计方案。

















