copyonwritearraylist 是写时复制的线程安全列表,读无锁、写加锁并复制数组,适用于读多写少场景;add 时加锁、复制扩容、volatile 写入新数组;迭代器基于快照,不抛 concurrentmodificationexception 但不保证实时性。

CopyOnWriteArrayList 是什么,为什么需要它
它不是普通线程安全的 ArrayList,而是一个「写时复制」容器,核心思想是:读操作完全无锁,写操作加锁并复制整个底层数组。适用于读多写少、且不能容忍读过程被写中断的场景(比如监听器列表、配置快照)。
常见误解是“它线程安全所以啥都能用”,其实它牺牲了实时性和内存开销来换读性能——每次 add、remove 都会新建数组,旧数组上的迭代器仍能看到旧数据,不会抛 ConcurrentModificationException,但也不会反映最新修改。
add 方法内部到底做了什么
add 操作全程在独占锁 ReentrantLock 下执行,关键步骤如下:
- 先获取当前数组
array的快照 - 创建新数组,长度为
oldLength + 1 - 用
System.arraycopy把旧数组内容拷贝过去 - 把新元素放在末尾
- 用
volatile写入新数组引用,确保其他线程可见
注意:add 不是原子地更新索引+数组,而是原子地替换整个数组引用。这意味着中间状态(如扩容中)对外不可见,但代价是每次写都要分配新数组。
立即学习“Java免费学习笔记(深入)”;
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();
}
}
迭代器为何不抛 ConcurrentModificationException
因为它的 Iterator 是弱一致性的,构造时就持有了当前数组的快照引用,后续所有 next()、hasNext() 都基于这个静态数组,和原容器后续的写操作完全无关。
这意味着:
- 迭代过程中即使其他线程调用了
add,你也不会看到新元素 - 也不会因结构变化触发检查失败——它压根不维护
modCount - 但反过来,如果你依赖迭代期间的数据实时性(比如监控类逻辑),它就不适用
它的 Iterator 是只读的:remove()、set() 等方法直接抛 UnsupportedOperationException。
什么时候不该用 CopyOnWriteArrayList
它不适合以下情况:
- 写操作频繁(比如每秒上千次
add),会导致大量数组复制和 GC 压力 - 集合很大(比如几十万元素),单次
add就要复制几 MB 内存 - 需要强一致性读(比如金融对账),因为它天然存在读延迟
- 想用
ListIterator进行双向遍历或修改——它不支持
替代方案要看场景:高并发读写均衡可考虑 ConcurrentLinkedQueue 或分段锁;需要强一致读写,老实用 synchronized 块包住 ArrayList 更可控。










