CopyOnWriteArrayList 内部实现:大白话解析
CopyOnWriteArrayList 就像 “多人协作时的文档快照” ,核心思想是 “写时复制” ,在修改数据时,先复制整个数组,修改副本,最后替换原数组。读操作无锁,写操作加锁,适合读多写少的场景(比如白板公告,大家常看但很少改)。
一、核心机制
-
读操作(如
get()) :- 直接读原数组,不加锁,无阻塞,高性能。
- 即使其他线程在修改数据,读线程看到的仍是旧数组(类似读文档快照)。
-
写操作(如
add()、set()、remove()) :- 加锁(
ReentrantLock),确保同一时间只有一个线程修改。 - 复制原数组,生成新数组(长度+1或-1)。
- 修改新数组,完成操作。
- 替换原数组引用,指向新数组。
- 解锁,其他线程后续读取新数组。
- 加锁(
示例:
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();
}
}
二、优缺点
| 优点 | 缺点 |
|---|---|
| 读操作极快(无锁、无阻塞) | 写操作慢(复制数组开销大) |
| 线程安全(读无需同步) | 内存占用高(频繁复制数组) |
| 避免读写冲突(读写分离) | 数据弱一致性(读旧数据) |
三、适用场景
-
高频读、低频写:
- 如系统配置、黑白名单(配置偶尔更新,大量查询)。
-
事件监听器列表:
- 监听器注册后频繁触发事件(读),但注册/注销(写)较少。
-
缓存数据快照:
- 缓存更新不频繁,但需要快速读取。
四、注意事项
-
迭代器弱一致性:
-
迭代器生成时持有旧数组的引用,遍历时看不到后续修改。
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) } -
-
避免频繁写操作:
- 若频繁修改(如每秒上千次),性能会急剧下降,建议改用
ConcurrentLinkedQueue或synchronizedList。
- 若频繁修改(如每秒上千次),性能会急剧下降,建议改用
-
内存敏感场景慎用:
- 大数组频繁复制可能导致内存压力或GC问题。
五、对比其他线程安全List
| 容器 | 锁机制 | 适用场景 |
|---|---|---|
synchronizedList | 全表锁 | 简单同步,读写均衡 |
CopyOnWriteArrayList | 写时复制(读写分离) | 读多写少,高频读操作 |
Vector | 全表锁(过时) | 遗留代码兼容 |
六、总结
CopyOnWriteArrayList 是读多写少的利器:
- 读操作无锁:直接访问数组,性能极高。
- 写操作安全:复制修改替换,隔离读写冲突。
- 代价:写性能差,内存占用高,数据弱一致。
口诀:
「写时复制真巧妙,读写分离性能高
读多写少最合适,内存开销要记牢
迭代器里旧数据,弱一致性别混淆!」