Java并发工具:CopyOnWriteArrayList
CopyOnWriteArrayList 是 Java 中 java.util.concurrent 包提供的一种线程安全的 List 实现。它特别适用于读多写少(Read-mostly)的并发场景,比如事件监听器列表、配置管理等。
核心思想
- 写时复制(Copy-On-Write)
每当对集合进行修改操作(如添加、删除、替换元素)时,它会创建一个新的数组副本,并在新副本上执行修改操作,最后再将引用指向新的数组。
而读取操作(如 get, iterator 等)不会加锁,直接访问当前数组。
- 线程安全实现
通过 volatile 关键字修饰底层数组,保证可见性;写操作通过锁(ReentrantLock)实现互斥。
每次写操作生成新数组,避免并发写冲突,但可能短暂存在多个数组副本。
特点
特性 | 描述 |
线程安全 | 所有操作都是线程安全的,不需要外部同步 |
写操作加锁 | 修改操作(add, set, remove)使用重入锁(ReentrantLock)保护 |
读不加锁 | 读操作无锁,性能高 |
最终一致性 | 迭代器或快照视图看到的是创建那一刻的数据状态 |
高开销写操作 | 每次写都要复制整个数组,适合写少读多的场景 |
不抛出 | 因为迭代基于快照 |
常用方法示例
import java.util.concurrent.CopyOnWriteArrayList;
public class CpListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素
list.add("A");
list.add("B");
list.add("C");
// 读取元素
System.out.println(list.get(0)); // 输出 A
// 删除元素
list.remove("B");
// 遍历(不会抛出 ConcurrentModificationException)
for (String s : list) {
System.out.println(s);
}
}
}
内部原理简析
1. 内部维护一个 volatile transient Object[] array; 来保存数据。
2. 每次写操作都会复制原数组到新数组并修改新数组。
3. 使用 ReentrantLock 来保证写操作的原子性和可见性。
4. 读操作直接访问 array,因为 volatile 能确保可见性。
适用场景
适合场景:
- 大量读操作,少量写操作
- 对实时一致性要求不高(最终一致即可)
不适合场景:
- 写操作频繁
- 数据量大(每次写都复制数组,性能差)
注意事项
- 内存开销:频繁写操作会导致大量数组复制,可能引发内存和 GC 压力。
- 实时性不足:读操作可能获取旧数据,不适合强一致性场景。
- 迭代器限制:迭代器不支持修改操作(如 remove、set),调用会抛出 UnsupportedOperationException。
与 Vector / Collections.synchronizedList 对比
特性 | Vector | Collections.synchronizedList | CopyOnWriteArrayList |
线程安全 | 是 | 是 | 是 |
读写是否加锁 | 读写都加锁 | 读写都加锁 | 仅写加锁 |
并发性能 | 差 | 一般 | 好(读并发强) |
是否抛 CME | 否 | 是 | 否 |
是否支持快照遍历 | 否 | 否 | 是 |