CopyOnWriteArrayList
一、核心原理
CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent)中提供的线程安全列表,基于 写时复制(Copy-On-Write, COW) 机制实现。其核心原理如下:
-
读写分离:
- 读操作:直接访问底层数组,无需加锁,支持高并发读取。
- 写操作(增、删、改):通过 复制底层数组 生成新副本,修改副本后替换原数组。此过程 需要加锁(使用
ReentrantLock),确保线程安全。
-
数据一致性:
- 写操作对原数组的修改 不会影响正在进行的读操作,读操作始终访问旧数组的稳定快照。
- 写操作完成后,后续的读操作会访问新数组。
-
内存模型:
- 底层数组由
volatile修饰,保证修改后的数组对所有线程可见。 - 每次写操作都会生成新数组,旧数组会被垃圾回收。
- 底层数组由
二、源码关键实现
1. 核心字段
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
final transient ReentrantLock lock = new ReentrantLock(); // 写操作锁
private transient volatile Object[] array; // 底层数组(volatile 保证可见性)
}
2. 写操作示例(add 方法)
public 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); // 替换原数组
return true;
} finally {
lock.unlock(); // 释放锁
}
}
3. 读操作示例(get 方法)
public E get(int index) {
return get(getArray(), index); // 直接访问数组,无需锁
}
private E get(Object[] a, int index) {
return (E) a[index];
}
三、适用场景
CopyOnWriteArrayList 适用于以下场景:
-
读多写少:
- 例如:缓存系统、配置管理、事件监听器列表等,读取频率远高于修改频率。
-
弱一致性容忍:
- 允许读取的数据是旧版本(如迭代期间列表被修改,迭代器仍访问旧数组)。
-
避免锁竞争:
- 读操作完全无锁,适合高并发读取环境,避免线程阻塞。
四、不适用场景
-
写多读少:
- 频繁的写操作会导致 内存复制开销大,性能急剧下降。
-
强一致性要求:
- 需要实时读取最新数据时(如股票交易系统),COW 的弱一致性不适用。
-
大对象存储:
- 数组元素为大型对象时,复制成本高,可能引发 内存溢出(OOM)。
五、常见问题与异常
1. 迭代器弱一致性
- 现象:迭代器遍历时,其他线程修改列表,迭代器无法感知新数据。
- 原因:迭代器持有旧数组的引用。
- 代码示例:
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3)); Iterator<Integer> it = list.iterator(); list.add(4); // 修改列表 while (it.hasNext()) { System.out.print(it.next() + " "); // 输出:1 2 3(不包含4) }
2. 内存占用过高
- 现象:频繁写操作导致旧数组未被及时回收,触发
OutOfMemoryError。 - 解决方案:
- 避免存储大型对象。
- 监控 JVM 堆内存,合理设置
-Xmx参数。
3. 写操作性能瓶颈
- 现象:高并发写操作时,线程因锁竞争阻塞。
- 解决方案:
- 改用
ConcurrentLinkedQueue或ConcurrentHashMap(适用于高并发写入场景)。 - 合并写操作(如批量添加)。
- 改用
六、与其他容器的对比
| 容器 | 线程安全机制 | 适用场景 | 缺点 |
|---|---|---|---|
CopyOnWriteArrayList | 写时复制 + 锁 | 读多写少,弱一致性 | 写性能差,内存占用高 |
Collections.synchronizedList | 方法级同步锁 | 简单同步需求 | 读写均加锁,并发性能低 |
Vector | 方法级同步锁 | 遗留代码兼容 | 性能差,不推荐新代码使用 |
ConcurrentLinkedQueue | CAS + 无锁 | 高并发写入,先进先出(FIFO) | 不支持随机访问 |
七、最佳实践
-
合理选择数据结构:
- 根据读写比例选择容器(如
ConcurrentHashMap适合写多读多场景)。
- 根据读写比例选择容器(如
-
避免长时间持有迭代器:
- 迭代器应尽快使用完毕,避免旧数组无法被回收。
-
监控内存与 GC:
- 使用 JVM 工具(如 VisualVM)监控堆内存,避免 OOM。
八、总结
CopyOnWriteArrayList 通过 写时复制 机制实现了读操作的无锁并发,适合读多写少的高并发场景,但其写操作的高内存开销和弱一致性特点需谨慎评估。在实际应用中,需结合业务需求权衡性能与一致性,合理选择并发容器。

2849

被折叠的 条评论
为什么被折叠?



