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就講到這裏了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章