1. 程式人生 > >JAVA併發容器:CopyOnWriteArrayList與CopyOnWriteArraySet

JAVA併發容器:CopyOnWriteArrayList與CopyOnWriteArraySet

生活

所有的程式設計師都劇作家,而所有計算機都是糟糕的演員。

CopyOnWriteArrayList介紹

還記得學集合的時候,學的第一個集合就是ArrayList.它是一個由陣列實現的集合。因為他對陣列的增刪改和查詢都是不加鎖的,所以它並不是執行緒安全的。
因此,我們會引入到一個執行緒安全的集合,Vector,他的底層也是陣列,與ArrayList不同的是,Vector裡對元素的增刪改查都是加了synchronized,因此說他是執行緒安全的。但是這種在任何場景都加鎖的集合顯然效率不高。
因此在多執行緒高併發環境下,引入了今天要研究的CopyOnWriteArrayList,他是一個寫時複製的一個集合,具體來說,它的底層還是一個數組,在查詢的時候直接查這個數組裡的元素,不需要加鎖,但是在做增刪改操作時,會獲取到互斥鎖,並且copy原陣列的資料到新陣列【在新陣列內進行增刪改操作】,執行完這些操作後,把集合陣列引用指向新陣列,因此在增刪改下不會對查詢產生影響,但是因為在做增刪改操作時每次都需要複製新陣列,非常耗費效能,因此這個集合適用在多讀少寫的場景,比如快取。
注意:如果你希望能馬上讀到剛寫進去的資料,就不適用這個集合了。

成員

    //互斥鎖
    transient final ReentrantLock lock = new ReentrantLock();

    //容器陣列
    private volatile transient Object[] array;

CopyOnWriteArrayList方法原始碼解析

簡單的看了一下CopyOnWriteArrayList的原始碼,非常簡單而且通俗易懂,這裡就貼一下增加資料和查詢資料的操作,主要理解一個解鎖,一個不加鎖,以及寫時複製的實現。

新增元素:
 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();
        }
    }

查詢

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

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
     //可以看到確實不加鎖
    public E get(int index) {
        return get(getArray(), index);
    }

CopyOnWriteArraySet

CopyOnWriteArraySet 是HashSet的併發實現。底層就是使用CopyOnWriteArrayList,要知道Set是不包含重複元素的,因此可以看到它底層實際新增元素的方法是呼叫了CopyOnWriteArrayList
addIfAbsent

public boolean addIfAbsent(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // Copy while checking if already present.
            // This wins in the most common case where it is not present
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = new Object[len + 1];
            for (int i = 0; i < len; ++i) {
            //如果有相同元素,直接放棄新增
                if (eq(e, elements[i]))
                    return false; // exit, throwing away copy
                else
                    newElements[i] = elements[i];
            }
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }