CopyOnWriteArrayList源码详解

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CopyOnWriteArrayList源码详解相关的知识,希望对你有一定的参考价值。

CopyOnWriteArrayList源码详解

最近在一个代码优化中使用到CopyOnWriteArrayList,想起这个java容器知道使用特性是读写分离,在每次写入时都复制一个新的list进行操作,但是没有具体的看过其源码细节,于是写一篇文章来记载下。

demo演示


首先写一个简单的程序演示一下,如下:

CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch countDownLatch = new CountDownLatch(9);
        for (int i = 0; i < 9; i++) 
            executorService.execute(() -> 
                Random random = new Random(System.currentTimeMillis());
                list.add(random.nextInt());
                countDownLatch.countDown();
            );
        
        countDownLatch.await();
        executorService.shutdown();
        System.out.println(list.toString());

这里输出就随意了,就不贴出输出结果了,直接看CopyOnWriteArrayList源码。

CopyOnWriteArrayList源码


首先看下CopyOnWriteArrayList的构造方法,进入构造方法。

/**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() 
        setArray(new Object[0]);
    
/**
     * Sets the array.
     */
    final void setArray(Object[] a) 
        array = a;
    

构造方法没有什么特别的地方,就是初始化一个空的Object数组。

接下来看add方法,add方法基本就是其关键所在,在add方法中实现数组复制写入,源码如下:

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return @code true (as specified by @link Collection#add)
     */
    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();
        
    

add方法比较简单,这里首先的操作就是对当前方法加锁。

final ReentrantLock lock = this.lock;
lock.lock();

然后获取当前数组,然后再进行数组的copy和新值的插入。

int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;

最后将array重新set入原数组中,这个setArray方法可能是进行两个数组合并工作,并首先进行了加锁操作,可以一起看一下。

/**
     * Sets the array.
     */
    final void setArray(Object[] a) 
        array = a;
    

比较简单,就是进行了array的赋值,那么能够肯定的就是remove操作必定也是进行的加锁操作,这样除了读取以外,其他操作都是需要进行锁的写入和删除的。

在新值添加完成后,最后进行unlock操作。

lock.unlock();

这里想必除了读取以外,其他的操作都是需要进行加锁操作的,可以简单看下get方法。

/**
     * @inheritDoc
     *
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    public E get(int index) 
        return get(getArray(), index);
    
    private E get(Object[] a, int index) 
        return (E) a[index];
    

操作比较简单,就是从array中读取值。

接下来看remove。

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).  Returns the element that was removed from the list.
     *
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    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();
        
    

remove方法开头与add相同,也是进行加锁操作,这样add、set、remove操作在同时就只有一个操作可以进行,同时也就避免了写入脏数据问题。

接下来的操作就比较简单了,进行了一个简单的数组的copy、value的替换和数组的重新赋值回原数组。

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

这里就不再对CopyOnWriteArrayList的set或者诸如toString()、sort方法进行讲解了,原理都类似,这里看一个比较有意思的方法,为subList(int fromIndex, int toIndex),获取数组中的一段范围。

/**
     * Returns a view of the portion of this list between
     * @code fromIndex, inclusive, and @code toIndex, exclusive.
     * The returned list is backed by this list, so changes in the
     * returned list are reflected in this list.
     *
     * <p>The semantics of the list returned by this method become
     * undefined if the backing list (i.e., this list) is modified in
     * any way other than via the returned list.
     *
     * @param fromIndex low endpoint (inclusive) of the subList
     * @param toIndex high endpoint (exclusive) of the subList
     * @return a view of the specified range within this list
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    public List<E> subList(int fromIndex, int toIndex) 
        final ReentrantLock lock = this.lock;
        lock.lock();
        try 
            Object[] elements = getArray();
            int len = elements.length;
            if (fromIndex < 0 || toIndex > len || fromIndex > toIndex)
                throw new IndexOutOfBoundsException();
            return new COWSubList<E>(this, fromIndex, toIndex);
         finally 
            lock.unlock();
        
    

这个方法直接返回了一个CowList对象,而不是一个list列表,可以看看CowList类。

private static class COWSubList<E>
        extends AbstractList<E>
        implements RandomAccess
    
        private final CopyOnWriteArrayList<E> l;
        private final int offset;
        private int size;
        private Object[] expectedArray;

        // only call this holding l's lock
        COWSubList(CopyOnWriteArrayList<E> list,
                   int fromIndex, int toIndex) 
            l = list;
            expectedArray = l.getArray();
            offset = fromIndex;
            size = toIndex - fromIndex;
        

        // only call this holding l's lock
        private void checkForComodification() 
            
        

        // only call this holding l's lock
        private void rangeCheck(int index) 
            
        

        public E set(int index, E element) 
           
        

        public E get(int index) 
            
        

        public int size() 
            
        

        public void add(int index, E element) 
            
        
    

COWSubList方法只是有offset、size等属性,并不是有真正的list中的值,并有list的相关的方法,那么在源list发生变化时,这个CowSubList也会发生相应的value的变化,这个在一些特殊的场景下确实比较有作用。

CopyOnWriteArrayList还有一些其他的特殊用法,例如返回listIterator之类的,这些都没有特别出奇的地方,就不详细讲述了。

CopyOnWriteArrayList用法除了在set、add、remove操作与别的list对应的方法不同外,其他基本倒是类似,因此CopyOnWriteArrayList就讲到这里了。

以上是关于CopyOnWriteArrayList源码详解的主要内容,如果未能解决你的问题,请参考以下文章

CopyOnWriteArrayList 使用入门及源码详解

详解CopyOnWrite容器及其源码

CopyOnWriteArrayList 源码解读

CopyOnWriteArrayList,CopyOnWriteArraySet源码分析

CopyOnWriteArrayList详解

Java并发编程之CopyOnWriteArrayList源码分析