CopyOnWriteArrayList源码分析

总结

总结放前面防止太长不看

  1. CopyOnWriteArrayList是一个线程安全、并且在读操作时无锁的List实现。
  2. CopyOnWriteArrayList内部通过volatile数组来存储数据,通过getArraysetArray维持语义,读操作不加锁,写操作复制原数组然后在复制的数组上面进行操作,完成操作之后再用新数组替换原数组。
  3. CopyOnWriteArrayList适合读多写少的并发场景,它的写操作效率极低,所有写操作共用一把锁,甚至在容量达到数组上限的时候添加元素只会用一个原数组的容量+1的新数组来代替它。
  4. 只能保证数据的最终一致性,不能保证数据的实时一致性。写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。

重要方法

add

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 仅仅return 内部 array
        Object[] elements = getArray();
        int len = elements.length;
        // 复制一个数组,并且容量+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        // 用新数组替换内部数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

get

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

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

可以看出无论在什么时候获取数组都是使用的getArray(),这个方法返回内部数组array,而对array的修改并不会在原数组上直接操作,而是先复制再操作,操作完之后替换原来的数组,这样不论在什么时候写和读都不会在同一个数组上操作而造成冲突。

set

public E set(int index, E element) {
    //写操作用的是同一把锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // 为了保持“volatile”的语义,任何一个读操作都应该是一个写操作的结果,
            // 也就是读操作看到的数据一定是某个写操作的结果(尽管写操作没有改变数据本身)。
            // 所以这里即使不设置也没有问题,仅仅是为了一个语义上的补充(就如源码中的注释所言)
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

remove

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            //移除的时候数组容量也是完全紧跟元素的数量,不留空间
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                                numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/copyonwritearraylist%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90/

发表评论

电子邮件地址不会被公开。