CopyOnWriteArrayList:写时复制¶
一、是什么¶
线程安全的 List,读操作无锁,写操作复制整个数组。
二、原理¶
读¶
直接读内部数组:
不加锁。
写¶
复制一份新数组,改完再替换引用:
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
为什么安全¶
- 写时 synchronized 保证只有一个线程在复制。
- 写完成后,新数组通过 volatile 引用替换,读线程立即看到。
- 读线程手里可能拿着旧数组,但读操作不受影响(不会 ConcurrentModificationException)。
三、优缺点¶
优点¶
- 读操作完全无锁,极高并发读。
- 迭代器不会抛 ConcurrentModificationException。
缺点¶
- 写操作贵:每次都复制整个数组。
- 内存占用:同时存在旧数组和新数组。
- 数据弱一致性:读可能读到旧值。
四、适用场景¶
- 读多写少:配置、监听器、白名单。
- 不适合写多的场景。
五、和 ArrayList 对比¶
| ArrayList | CopyOnWriteArrayList | |
|---|---|---|
| 线程安全 | ❌ | ✅ |
| 读性能 | 快 | 快(无锁) |
| 写性能 | 快(尾插均摊 O(1)) | 慢(O(n)) |
| 迭代 | 可能 CME | 不会 CME |
| 内存 | 少 | 多(双数组) |
注意
CopyOnWriteArrayList 的迭代器是快照,不会反映写后的变化。这就是"弱一致性"。