CopyOnWriteArrayList 底层也是通过一个数组保存数据,使用 volatile 关键字修饰数组,保证当前线程对数组对象重新赋值后,其他线程可以及时感知到。

private transient volatile Object[] array;

在写入操作时,加了一把互斥锁 ReentrantLock 以保证线程安全。

public boolean add(E e) {
    //获取锁
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
        //获取到当前List集合保存数据的数组
        Object[] elements = getArray();
        //获取该数组的长度(这是一个伏笔,同时len也是新数组的最后一个元素的索引值)
        int len = elements.length;
        //将当前数组拷贝一份的同时,让其长度加1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //将加入的元素放在新数组最后一位
        setArray(newElements);
        return true;
    } finally {
        //释放锁
        lock.unlock();
    }
}
Arrays.copyOf(elements,len+1);

它复制数组的核心目的不是“预留容量”,而是实现 Copy-On-Write 写时复制。

所以也正因为如此:

CopyOnWriteArrayList 写操作成本很高,每次 add 都可能复制整个数组,因此特别适合读多写少,而不适合频繁写入。

Q:CopyOnWriteArrayList 是如何实现线程安全的

CopyOnWriteArrayList 内部通过 volatile 的 Object 数组保存数据。读操作通常直接读取当前数组,不加写锁;写操作首先通过 ReentrantLock 保证多个写线程互斥,然后使用 Arrays.copyOf 复制当前数组,在新数组上完成修改,最后通过 setArray 将 volatile 数组引用指向新数组,并在 finally 中释放锁。因此它实现了写时复制,读写之间干扰较少,但写操作需要复制整个数组,所以适合读多写少的场景。