2024-03-23
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1325422357

回答

CopyOnWriteArrayList 是一个线程安全的 ArrayList,其核心思想是“写时复制”,即每当我们对列表进行修改(添加、删除、更新元素)时,它都会先创建该列表的一个副本,然后在这个副本上进行修改。修改完成后,它会将原来的列表引用指向新修改过的副本。这种机制有效减少了线程之间的竞争,因为读操作可以安全地访问列表,而不需要担心写操作同时发生的情况。

copy-on-Write 体现的是一种读写分离的思想。

CopyOnWriteArrayList 实现原理

原理

写时复制原理图:

CopyOnWriteArrayList 的实现原理比较简单,源码也很简单,我们大致看下就可以了。

  • 结构

CopyOnWriteArrayList 使用一个 volatile 类型的数组来存储数据,volatile 保证这个数组引用的可见性,当数组发生变化时,其他所有线程都能立即看到这个变化。

private transient volatile Object[] array;

同时为了保证线程安全,CopyOnWriteArrayList 新建了一个 Object 锁对象。

final transient Object lock = new Object();
  • 添加元素

当我们需要添加一个元素时,CopyOnWriteArrayList会先锁定整个类(利用 object 锁对象),然后复制一个新的数组,大小为原数组大小加 1,然后将新元素添加到新数组的末尾,最后再将引用指向新的数组。

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

这里为什么只 + 1 呢?因为 CopyOnWriteArrayList 的使用场景是读多写少的场景,旨在优化读操作,选择牺牲了写的性能。只 + 1 的原因是为了减少内存占用和复制成本。如果每次增加的空间过多,将导致不必要的内存浪费,特别是在元素添加不频繁的情况下。有小伙伴说,多加点又如何呢?多加点说明你写的场景有点儿多,这个时候你要考虑的就不是 CopyOnWriteArrayList 了。

  • 读取元素

CopyOnWriteArrayList 旨在优化读操作,所以读取是无锁的。由于所有的修改操作都会创建一个新的数组,所以不会出现并发问题。

    public E get(int index) {
        return elementAt(getArray(), index);
    }

缺点

  • 内存占用和复制开销CopyOnWriteArrayList 在每次修改时都需要复制整个列表,这可能会导致较高的内存占用和复制开销。因此,如果列表较大或者写操作频繁,CopyOnWriteArrayList 可能不是好的选择。
  • 数据一致性问题::CopyOnWriteArrayList 提供的迭代器是弱一致性的。这意味着在遍历元素时是基于列表的某个固定版本进行的,即使后续有修改操作,迭代器看到的内容也不会改变。
阅读全文