一、一句话理解
CopyOnWriteArrayList是一个线程安全的 List,它通过“写时复制”(Copy-On-Write)策略实现并发控制:所有修改操作(add/set/remove 等)都会创建底层数组的一个全新副本,而读操作(get/iterator/size 等)直接访问当前快照,无需加锁。
二、核心设计思想
1.写时复制(Copy-On-Write)
- 读操作无锁:因为底层数组是
volatile的,读操作看到的是某个“瞬间快照”,天然线程安全。 - 写操作加锁 + 复制:
- 使用
ReentrantLock保证同一时间只有一个写操作。 - 修改时,不改动原数组,而是:
- 拷贝一份新数组(
Arrays.copyOf) - 在新数组上做修改
- 将
array引用原子地指向新数组(volatile写)
- 拷贝一份新数组(
- 使用
publicbooleanadd(Ee){finalReentrantLocklock=this.lock;lock.lock();try{Object[]elements=getArray();// 旧数组Object[]newElements=Arrays.copyOf(elements,len+1);// 复制newElements[len]=e;// 修改副本setArray(newElements);// volatile 写,切换引用returntrue;}finally{lock.unlock();}}2.弱一致性迭代器(Snapshot Iterator)
- 调用
iterator()时,会捕获当前array的引用(快照)。 - 迭代过程中:
- 不会抛出
ConcurrentModificationException - 看不到之后的修改(因为用的是旧数组)
- 不支持
remove()/set()/add()→ 抛UnsupportedOperationException
- 不会抛出
publicIterator<E>iterator(){returnnewCOWIterator<E>(getArray(),0);// 传入当前 array 快照}3.适用场景
✅读多写少(如监听器列表、配置项缓存)
✅遍历时不能加锁(避免死锁或性能瓶颈)
❌写操作频繁→ 每次复制数组开销大(O(n) 时间 + 内存)
❌实时性要求高→ 迭代器看不到最新修改
三、关键特性总结
| 特性 | 说明 |
|---|---|
| 线程安全 | 所有可变操作加锁,读操作无锁但可见性由volatile保证 |
| 内存一致性 | happens-before:写入前的操作对后续读取该元素的线程可见 |
| 允许 null 元素 | null被当作普通元素处理 |
| fail-safe 迭代器 | 基于快照,不抛ConcurrentModificationException |
| 高写成本 | 每次写都复制整个数组,适合小规模、低频修改 |
| 最终一致性 | 读操作可能看到“过期”数据,但不会出错 |
四、与Vector/Collections.synchronizedList对比
CopyOnWriteArrayList | Vector/synchronizedList | |
|---|---|---|
| 读性能 | ⭐⭐⭐ 极高(无锁) | ⭐ 需要获取锁 |
| 写性能 | ⭐ 极低(复制整个数组) | ⭐⭐ 中等(只锁方法) |
| 迭代行为 | 快照,不阻塞写,不抛异常 | 需手动同步,否则可能抛ConcurrentModificationException |
| 内存占用 | 高(写时双倍内存) | 低 |
五、使用示例
// 典型场景:事件监听器列表publicclassEventBus{privatefinalList<Listener>listeners=newCopyOnWriteArrayList<>();publicvoidaddListener(Listenerl){listeners.add(l);// 安全}publicvoidfireEvent(Evente){// 遍历时其他线程可安全增删监听器for(Listenerl:listeners){l.onEvent(e);}}}六、注意事项
- 不要用于大数据量列表:写操作 O(n) 复制,GC 压力大。
- 不要依赖迭代器实时性:它反映的是调用
iterator()时的状态。 - 组合操作非原子:如
if (!list.contains(x)) list.add(x)不是原子的,需外部同步。
七、源码亮点
volatile Object[] array:保证数组引用的可见性。ReentrantLock lock:细粒度控制写操作。COWIterator:实现快照语义。addIfAbsent/addAllAbsent:提供“去重添加”语义,内部也基于快照判断。
总结
CopyOnWriteArrayList是为高并发读、低频写场景量身定制的线程安全容器。它用空间换时间 + 最终一致性的思路,巧妙避免了读写冲突,是并发编程中“乐观锁”思想的经典体现。
如果你正在处理类似“观察者列表”、“白名单配置”等场景,它往往是比synchronizedList更优的选择。