CopyOnWriteArrayList 原理与实现

核心原理

CopyOnWriteArrayList 采用 写时复制 策略,读操作不加锁,写操作复制整个数组。

写时复制机制

CopyOnWriteArrayList 写操作流程:
┌─────────────────────────────────────────────────────────────┐
│                    初始状态                                  │
│  array = [A, B, C, D]  ← volatile引用                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    写操作(添加元素E)                        │
│  1. 加锁                                                    │
│  2. 复制原数组:newArray = [A, B, C, D]                     │
│  3. 新数组尾部添加:newArray = [A, B, C, D, E]              │
│  4. 原子替换引用:array = newArray                          │
│  5. 解锁                                                    │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    读操作(无锁)                            │
│  读取的始终是旧数组的快照,不阻塞写操作                       │
└─────────────────────────────────────────────────────────────┘

核心优势:读写分离,读操作完全无锁,适用于ConcurrentHashMap不适用的遍历一致性场景。

核心实现

添加操作

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();
    }
}

读取操作

public E get(int index) {
    // 读操作完全无锁
    return get(getArray(), index);
}
 
private E get(Object[] a, int index) {
    return (E) a[index];
}

批量写入优化

public boolean addAll(Collection<? extends E> c) {
    Object[] cs = c.toArray();
    if (cs.length == 0)
        return false;
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        
        // 只需复制一次,优化批量写入
        Object[] newElements = Arrays.copyOf(elements, len + cs.length);
        System.arraycopy(cs, 0, newElements, len, cs.length);
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

适用场景

场景推荐度说明
配置列表(写极少,读频繁)✅ 强烈推荐读操作零开销
事件监听器列表✅ 推荐遍历期间可安全修改
商品分类/标签列表✅ 推荐数据相对稳定
频繁写入的场景❌ 不推荐每次写都复制数组,性能差

与其他写时复制技术对比

CopyOnWriteArrayList 的写时复制思想也可以应用于 CopyOnWriteArraySet(基于 COW 实现)。写时复制的核心权衡:

  • 内存:写操作期间同时持有新旧数组
  • 一致性:迭代器遍历的是创建时的快照,不会反映后续修改
  • 适用:读多写少、数据量可控的场景

相关链接