面试知识库
进阶

CopyOnWriteArrayList原理#

一句话答案#

写时复制:读无锁直接读数组,写加锁复制新数组修改后替换原数组,适合读多写少场景(如监听器列表)。

核心要点

核心原理:

  • :无锁,直接读取内部 Object[] array(volatile 保证可见性)
  • :加 ReentrantLock → 复制底层数组 → 修改副本 → 替换原数组 → 释放锁
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] old = getArray();
        Object[] newArr = Arrays.copyOf(old, old.length + 1);
        newArr[old.length] = e;
        setArray(newArr);  // volatile 写
        return true;
    } finally { lock.unlock(); }
}
java

优缺点:

  • ✅ 读无锁,并发读性能极高
  • ✅ 迭代器不会抛 ConcurrentModificationException
  • ❌ 写操作慢(复制整个数组)
  • ❌ 内存占用(写时两份数组)
  • ❌ 数据弱一致性(读到旧数据)

适用场景: 事件监听器列表、配置项缓存(读远多于写)

面试回答(2分钟版)

CopyOnWriteArrayList 是 JUC 提供的线程安全 List,核心思想是写时复制:读操作直接读取内部的 volatile 数组引用,完全无锁无开销;写操作先加 ReentrantLock,然后复制一份新数组,在新数组上做修改,最后通过 volatile 写替换原数组引用,释放锁。这样读线程始终读的是修改前的快照,不会受写操作影响,也不会抛出 ConcurrentModificationException。代价是写操作需要复制整个数组,时间复杂度 O(n),而且写入期间内存中同时存在新旧两份数组。另一个特点是弱一致性:读线程可能读到旧数据,因为读的是写入前的数组快照。所以 CopyOnWriteArrayList 只适合读多写极少的场景,比如事件监听器列表、配置项缓存、黑白名单等。如果写操作频繁应该用 Collections.synchronizedList 或者 ConcurrentLinkedQueue 等其他并发容器。

追问与易错

追问方向:

  • “写时复制的内存开销大吗?”→ 每次写操作复制整个数组,写入期间新旧两份数组同时存在,数组越大内存开销越大,所以只适合小数据量且写操作极少的场景
  • “迭代器是强一致还是弱一致?”→ 弱一致性,迭代器持有的是创建时的数组快照,遍历期间其他线程的写入不可见,不会抛 ConcurrentModificationException
  • “什么场景适合用?”→ 读多写极少的场景,如事件监听器列表、配置项缓存、黑白名单;写频繁应使用 Collections.synchronizedList 或 ConcurrentLinkedQueue

易错点:

  • ❌ 写也是 O(1)——每次写复制整个数组 O(n)
  • ❌ 在写多场景使用——性能极差