深入理解CopyOnWriteArrayList源码分析

上篇推荐:Java中快速失败 (fail-fast) 机制

CopyOnWriteArrayList简介

CopyOnWriteArrayList是java.util.concurrent包下提供的一个线程安全的ArrayList。它通过一个简单的策略来保证线程安全:当我们需要修改列表时(增加、删除、修改等操作),而不是直接对当前的内容进行操作,它会将当前的内容复制一份,在副本上执行修改然后将原列表指向新的副本

源码分析与加锁机制

CopyOnWriteArrayList使用了ReentrantLock来实现线程安全的修改操作。下面是一个简化版的源码展示其加锁机制:

private transient volatile Object[] array;
final transient ReentrantLock lock = new ReentrantLock();

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

如上述代码所示,每一个修改操作都是在加锁的情况下执行的。首先复制当前的数组,然后在副本上执行修改,最后再将原来的数组引用指向新的副本。重要的是,获取数组的当前状态不需要加锁,因为不涉及修改操作。

增删查改实现

增加

如前所示,增加操作首先复制出一个新的数组,然后在新数组的末尾添加新的元素,并将原数组指向这个新数组。

删除

删除操作也是首先复制数组,然后从复制出的新数组中移除指定的元素,完成后,再将原数组指向新数组。

查找

查找操作是CopyOnWriteArrayList中效率最高的操作,因为它可以直接访问底层数组,无需加锁:

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

private E get(Object[] a, int index) {
    return (E) a[index];
}

修改

修改操作和增加、删除操作类似,首先是数组的复制,然后在新数组上修改指定位置的元素值,之后再将原数组指向新数组。

适用场景

CopyOnWriteArrayList适合读多写少的并发场景。由于它的读取操作不需要加锁,可以充分利用硬件和操作系统的优化,如缓存一致性协议,使得读取操作非常高效。而写操作,由于涉及到复制整个数组,所以在数据量大、写操作频繁的场景中性能会相对较低。

问题

  • 内存消耗:由于写操作需要复制整个数组,对于大规模数据的列表,这可能导致相当昂贵的内存消耗。
  • 数据一致性:CopyOnWriteArrayList保证最终一致性而非实时一致性。在写操作发生的同时,读取操作可能仍然读到旧的数据。
  • 迭代器弱一致性:迭代器不会反映出在迭代器创建之后的修改。

迭代器弱一致性 举例说明

public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        Iterator<String> iterator = list.iterator();

        // 在迭代进行中对列表进行修改
        list.add("d");
        list.remove("b");

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
a
b
c

在迭代的过程中,迭代器只能看到最开始的快照,可能会错过后来添加或删除的元素。因此,虽然不会抛出异常,但迭代的结果可能是一个旧的快照,从而出现了迭代的弱一致性。

总的来说,CopyOnWriteArrayList是一种读写分离的思路,在特定的适用场景下可以提供很好的并发性能和线程安全性。然而,开发者在使用时需要注意它的限制和适用的局限性,以确保应用程序的性能和正确性。

你可能感兴趣的:(Java,java,spring,cow)