CopyOnWriteArrayList源碼閱讀

一、CopyOnWrite容器有兩種:
·CopyOnWriteArrayList
·CopyOnWriteArraySet
CopyOnWrite容器簡稱COW容器,其特色以下:
1)CopyOnWrite容器即寫時複製的容器。
2)通俗的理解是當咱們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,而後新的容器裏添加元素,添加完元素以後,再將原容器的引用指向新的容器,讀時都是訪問舊容器,寫時才須要加鎖。
3)這樣作的好處是咱們能夠對CopyOnWrite容器進行併發的讀,而不須要加鎖,由於當前容器不會添加任何元素。
因此CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不一樣的容器。
二、CopyOnWriteArrayList類繼承結構:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable;java

三、對於CopyOnWriteArrayList主要查看如下幾個方法:
·建立:CopyOnWriteArrayList()
·添加元素:即add(E)方法
·獲取單個對象:即get(int)方法
·刪除對象:即remove(E)方法
·遍歷全部對象:即iterator(),在實際中更經常使用的是加強型的for循環去作遍歷。數組

四、構造方法CopyOnWriteArrayList,其相關代碼:
/* 只能 getArray/setArray訪問數組. /
private volatile transient Object[] array;併發

/**
 * 獲取數組
 */
final Object[] getArray() {
    return array;
}

/**
 * 設置數組
 */
final void setArray(Object[] a) {
    array = a;
}

/**
 *建立一個空的數組,Object[0],而ArrayList則是10
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

五、添加元素
public boolean add(E e) {dom

final ReentrantLock lock = this.lock;
    lock.lock(); //獲取全局鎖(獨佔鎖),獲取不到則阻塞
    try {
        Object[] elements = getArray(); //獲取當前的數組
        int len = elements.length;
                     /*
         * Arrays.copyOf(elements, len + 1)的大體執行流程:
         * 1)建立新數組,容量爲len+1,
         * 2)將舊數組elements拷貝到新數組,
         * 3)返回新數組
         */
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e; //新數組的末尾元素設成e
        setArray(newElements); //將舊數組指向新數組引用
        return true;
    } 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); //先獲取數組,在讀取指定位置的元素。
}

        從以上代碼能夠看出,讀取元素時不加鎖的,此時採用的是弱一致性策略。獲取指定位置的元素分爲兩步,首先獲取到當前list裏面的array數組,這裏稱爲步驟1,而後經過隨機訪問的下標方式訪問指定位置的元素,這裏稱爲步驟2。
        由於整個過程並無加鎖,這就可能會致使當執行完步驟1後執行步驟2前,另一個線程C進行了修改操做,好比remove操做,就會進行寫時拷貝刪除當前get方法要訪問的元素,而且修改當前list的array爲新數組。而這以後步驟2 可能纔開始執行,步驟2操做的是線程C刪除元素前的一個快照數組(由於步驟1讓array指向的是原來的數組),因此雖然線程C已經刪除了index處的元素,可是步驟2仍是返回index處的元素,這其實就是寫時拷貝策略帶來弱一致性。

七、修改指定元素,若元素不存在則拋出ndexOutOfBoundsException。
/**ide

  • Replaces the element at the specified position in this list with the
  • specified element.
  • @throws IndexOutOfBoundsException {@inheritDoc}
    */
    public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();//獨佔鎖
    try {
    Object[] elements = getArray();
    E oldValue = get(elements, index); //獲取指定位置的元素ui

    if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element; //更新指定位置上的元素
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics  同一對象,則不更新,
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }

    }
    若是指定位置元素與新值同樣,則爲了保障volatile語義,仍是須要從新設置下array,雖然array內容並無改變(爲了保證 volatile 語義是考慮到 set 方法自己應該提供 volatile 的語義).。this

    七、刪除元素
        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];
                          //分兩次拷貝刪除後的數組
                            //先複製index位置以前的元素,
            System.arraycopy(elements, 0, newElements, 0, index); //index在此處表示要複製的長度或元素個數
                            //複製inex位置以後的元素
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
                            //其中:src表示源數組,srcPos表示源數組要複製的起始位置,desc表示目標數組,length表示要複製的長度。
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock(); //操做完成,解除鎖定
    }

    }線程

    八、弱一致性的迭代器

    /**指針

  • Returns an iterator over the elements in this list in proper sequence. 返回必定順序的的元素列表(迭代器)
  • <p>The returned iterator provides(提供) a snapshot(快照) of the state of the list
  • when the iterator was constructed. No synchronization is needed while
  • traversing the iterator. The iterator does <em>NOT</em> support the
  • <tt>remove</tt> method. 大概意思就是:返回的迭代器是list列表的一個快照,在整個迭代的過程當中不須要同步(加鎖),這個迭代器不支持remove操做。
  • @return an iterator over the elements in this list in proper sequence
    */
    public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
    }code

    /**

  • {@inheritDoc}
  • <p>The returned iterator provides a snapshot of the state of the list
  • when the iterator was constructed. No synchronization is needed while
  • traversing the iterator. The iterator does <em>NOT</em> support the
  • <tt>remove</tt>, <tt>set</tt> or <tt>add</tt> methods. 不支持remove/set/add等操做
    */
    public ListIterator<E> listIterator() {
    return new COWIterator<E>(getArray(), 0);
    }

    /**

  • {@inheritDoc}
  • <p>The returned iterator provides a snapshot of the state of the list
  • when the iterator was constructed. No synchronization is needed while
  • traversing the iterator. The iterator does <em>NOT</em> support the
  • <tt>remove</tt>, <tt>set</tt> or <tt>add</tt> methods.
  • @throws IndexOutOfBoundsException {@inheritDoc}
    */
    public ListIterator<E> listIterator(final int index) {
    Object[] elements = getArray();
    int len = elements.length;
    if (index<0 || index>len)
    throw new IndexOutOfBoundsException("Index: "+index);

    return new COWIterator<E>(elements, index);

    }

    private static class COWIterator<E> implements ListIterator<E> {
    /* Snapshot of the array /array數組的一個快照
    private final Object[] snapshot;
    /* Index of element to be returned by subsequent call to next. /將由後續調用next返回的元素的索引。即數組下標或索引
    private int cursor;

    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }
    
    //判斷是否有下個元素
    public boolean hasNext() {
        return cursor < snapshot.length;
    }
    
            ......
    //獲取當前元素,索引加1
    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
            .....

    }

    這裏爲何說snapshot是list的快照呢?明明是指針傳遞的引用,而不是拷貝。若是在該線程使用返回的迭代器遍歷元素的過程當中,其餘線程沒有對list進行增刪改,那麼snapshot自己就是list的array,由於它們是引用關係。

    可是若是遍歷期間,有其餘線程對該list進行了增刪改,那麼snapshot就是快照了,由於增刪改後list裏面的數組被新數組替換了,這時候老數組只有被snapshot所引用,因此這也就說明獲取迭代器後,使用改迭代器進行遍歷元素時候,其它線程對該list進行的增刪改是不可見的,由於它們操做的是兩個不一樣的數組,這也就是弱一致性的達成。

相關文章
相關標籤/搜索