(6)併發編程高級篇-CopyOnWriteArrayList

介紹

java併發包中的併發List只有CopyOnWriteArrayList,CopyOnWriteArrayList的修改操作底層都是通過拷貝一份數組進行的。使用ReentrantLock獨佔鎖來保證多線程併發下只有一個線程進行修改操作,下面我們通過源碼分析來逐步瞭解。

初始化方法

   /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

無參構造器中,創建了一個長度爲1Object數組.

添加元素

在這裏插入圖片描述
從上圖可知CopyOnWriteArrayList的添加元素方法有7個,我們着重講下add(E) 這個方法,其他方法的實現原理大同小異。


    /**
     * 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) {
         //1. 獲取重入鎖(獨佔鎖)
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //2. 獲取array
            Object[] elements = getArray();
            int len = elements.length;
            //3. 複製原array到新array,並添加元素
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //4. 將新array替換原array
            setArray(newElements);
            return true;
        } finally {
           //5. 釋放重入鎖
            lock.unlock();
        }
    }

從1 和 5 可以看出 的方法,通過獨佔鎖的機制,保證多線程併發修改元素時,只能有一個線程進行,其他線程會阻塞掛起,從而確保了此方法的原子性。
2 、3 處 通過複製原array到新array中,通過對新array進行添加元素操作,此處可知CopyOnWriteArrayList是一個無界的隊列。4 處 將 新數組替換了原數組

刪除元素

在這裏插入圖片描述
CopyOnWriteArrayList 有6個刪除方法,我們着重講解下remove(int)這個方法。

  /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left 
     *   Returns the element that was removed from the list.
     * 翻譯:刪除列表中指定位置的元素,往左邊移動隨後的元素,返回從列表中刪除的元素。
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
    //1. 獲取鎖
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //2. 獲取原array
            Object[] elements = getArray();
            //3. 獲取要刪除的元素
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;

            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
            //4. 通過兩次複製,來刪除元素並整合新的array
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
              //5. 新array替換原array
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

獲取指定位置元素

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

在如上代碼中,當線程threadA 執行get(int index ),是分兩個步驟:1. 獲取數組 2. 通過數組下標index索引獲取指定元素,它會存在數據弱一致性的問題。當線程threadB 在 線程 threadA執行步驟1後和步驟2之前時,刪除了threadA 要訪問的元素(假設爲x元素),那麼threadA執行步驟2時獲取的是x元素,而不是其他的。因爲threadA在執行步驟1後,操作的是原數組。

總結

CopyOnWriteArrayList通過寫時複製來保證數據的一致性,而它獲取、修改、寫入這三個操作並不是原子性,所以通過獨佔鎖來保證任何一時刻只能有一個線程對數據進行操作。另外CopyOnWriteArraySet的實現原理和CopyOnWriteArrayList也是類似的。

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