CopyOnWriteArrayList分析


  CopyOnWriteArrayList是一個在多線程操作中線程安全的ArrayList的一個變種,她在所有對ArrayList對象的編輯操作(add,set等)都會復制一份副本,因此無論是對ArrayList操作還是對其iterator操作都不會拋ConcurrentModificationException異常。

使用場景

  CopyOnWriteArrayList通常適用於讀多寫少的場景,對每次寫操作都會復制一份數據的副本,因此不會影響原先數據的讀操作。雖然每次復制副本會耗費時間,但相對於使用synchronize來保證線程安全,在特定場景下效果還是不錯的。

源碼分析

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

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

  CopyOnWriteArrayList維護一個ReentrantLock鎖,主要用於保證同一時間只能有一個線程對array數據進行復制編輯操作(set,add等),避免多線程下對數據復制操作造成數據不一致現象。 同時array聲明為volatile,保證線程讀取數據時將內存的數據刷新至緩存,從而得到最新數據。寫入數據時保證最新數據寫入到內存。在CopyOnWriteArrayList所有操作中,獲取數據和寫入數據不是直接使用this.array=array,而是使用getArray()和setArray()操作,刷新一下緩存,保證獲得的是最新數據及將最新數據寫入內存。

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

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

  CopyOnWriteArrayList所有的讀操作都是線程安全的,因為每次讀操作讀的都是元數據的一個snapshot。同時由getArray()來保證讀到的數據都是最新版本的。

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

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

  再看一下編輯操作,這里以add()和remove()為例,可以看到每次只能有一個線程可以對ArrayList進行修改,而在修改之前通過getArray()來保證獲取最新的array數據,然后復制一份array的副本newElements(復制過程根據具體操作而定,例如add會全部復制並預留一個位置,而remove則部分復制),對副本進行編輯后再通過setArray將最新的副本賦給array。這樣其他線程再進行讀操作時通過getArray()獲取的就是最近編輯過的數據。

COWIterator

  傳統的ListIterator在遍歷列表時如果列表被修改會有fast-fail機制來保證線程安全,而於CopyOnWriteArrayList已經具備線程安全性,並且CopyOnWriteArrayList的Iterator更多的用於數據的遍歷,所以其關閉了iterator對數據的編輯功能.

/**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code remove}
         *         is not supported by this iterator.
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }

 

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code set}
     *         is not supported by this iterator.
     */
    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code add}
     *         is not supported by this iterator.
     */
    public void add(E e) {
        throw new UnsupportedOperationException();
    }

 

總結

  • CopyOnWriteArrayList通過編輯操作復制元數據副本的方式成功避免了多線程操作List線程不安全的問題,同時通過聲明array[]為volitile類型保證線程每次讀取的數據都是最新數據。
  • 由於每次編輯操作都會復制一份副本,因此CopyOnWriteArrayList只適用於讀多改少的場景,在其他場景中還是建議使用synchronize或者Collections.SynchronizedList來保證線程安全
  • CopyOnWriteArrayList不能保證線程遍歷的數據一定是最新的數據,因此是能適用於實時性要求不高的場景。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM