CopyOnWriteArrayList 是 Java 并发包中提供的线程安全列表,采用 写时复制(Copy-On-Write) 策略实现高并发读取,适用于 读多写少 的场景。本文将从数据结构、核心操作、优缺点等方面进行详细解析其实现机制。
一、数据结构
-
底层存储
-
使用
volatile
修饰的数组保存元素,确保多线程可见性:javaprivate transient volatile Object[] array;
-
所有操作均基于此数组的快照,写操作会复制并替换原数组。
-
-
锁机制
-
通过
ReentrantLock
保证写操作的原子性:javafinal transient ReentrantLock lock = new ReentrantLock();
-
二、核心操作
1. 写操作(Add/Set/Remove)
-
步骤:
- 加锁 :获取
ReentrantLock
。 - 复制数组:创建原数组的副本。
- 修改副本:在副本上执行写操作(如添加、删除元素)。
- 替换原数组 :将
volatile
数组引用指向新副本。 - 释放锁:解锁,允许其他写操作进行。
- 加锁 :获取
-
示例(add 方法):
javapublic boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); // volatile 写,保证可见性 return true; } finally { lock.unlock(); } }
2. 读操作(Get/Iterate)
-
无锁访问:直接读取当前数组,无需同步。
-
示例(get 方法):
javapublic E get(int index) { return get(getArray(), index); // 直接访问 volatile 数组 }
3. 迭代器
-
弱一致性:迭代器基于创建时的数组快照,不会反映后续修改。
-
实现代码:
javapublic Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); }
COWIterator
内部保存快照数组,遍历期间不受写操作影响。
三、性能与适用场景
场景 | 性能表现 | 示例应用 |
---|---|---|
高频读取 | 极高(无锁,直接访问数组) | 事件监听器列表、配置信息缓存 |
低频写入 | 较低(复制数组开销大) | 动态添加少量监听器或配置项 |
迭代遍历 | 安全但滞后(基于旧快照) | 日志记录、批量读取操作 |
四、优缺点分析
优点
- 线程安全:写操作通过锁和复制保证原子性,读操作无锁。
- 无并发修改异常 :迭代器遍历旧快照,不会抛出
ConcurrentModificationException
。 - 高读性能:读操作无锁,适合读多写少场景。
缺点
- 写性能差 :每次写操作需复制整个数组,时间复杂度为 O(n) 。
- 内存占用高:频繁写操作导致内存碎片和临时对象增加。
- 数据延迟:迭代器和读操作可能访问旧数据,无法保证强一致性。
五、对比其他线程安全列表
实现类 | 锁机制 | 读性能 | 写性能 | 一致性 |
---|---|---|---|---|
CopyOnWriteArrayList |
写时复制 + 锁 | 极高 | 低 | 弱一致性(快照) |
Vector |
全表锁 | 低 | 中等 | 强一致性 |
Collections.synchronizedList |
全表锁 | 低 | 中等 | 强一致性 |
六、使用建议
-
适用场景
- 监听器管理(如 GUI 事件监听)。
- 读多写少的配置或缓存数据。
- 需要避免迭代时并发修改异常的场合。
-
避坑指南
- 避免频繁写操作 :如实时数据更新,应选择
ConcurrentLinkedQueue
或ConcurrentHashMap
。 - 监控内存使用:大数组频繁复制可能导致 GC 压力。
- 慎用批量写入:尽量合并多次写操作,减少复制次数。
- 避免频繁写操作 :如实时数据更新,应选择
七、源码关键点
-
数组替换
javafinal void setArray(Object[] a) { array = a; // volatile 写,保证线程可见性 }
-
锁的使用
- 所有修改操作(
add
、set
、remove
)均通过ReentrantLock
同步。
- 所有修改操作(
通过写时复制策略,CopyOnWriteArrayList
在特定场景下实现了高效的线程安全读取,是 Java 并发编程中处理读多写少问题的经典设计。开发者需根据实际需求权衡其优缺点,合理选择数据结构。