併發容器學習—CopyOnWriteArrayList與CopyOnWriteArraySet

1、CopyOnWriteArrayList併發容器java

1.CopyOnWriteArrayList的底層數據結構數組

    CopyOnWriteArrayList的底層實現是數組,CopyOnWriteArrayList是ArrayList的一個線程安全的變體,其增刪改操做都是經過對底層數組的從新複製來實現的。這種容器內存開銷很大,可是在讀操做頻繁(查詢),寫操做(增刪改)極少的場合卻極用,而且每一次寫操做都是在一塊新的空間上進行的,不存在併發問題。安全

 

2.CopyOnWriteArrayList的繼承關係數據結構

    CopyOnWriteArrayList繼承關係以下圖所示,List接口和Collection接口在學習ArrayList時已經分析過,不在多說。併發

3.重要屬性和構造方法dom

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    //可重入鎖
    final transient ReentrantLock lock = new ReentrantLock();

    //底層實現,用於存放數據的數組
    private transient volatile Object[] array;

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

    //修改數組
    final void setArray(Object[] a) {
        array = a;
    }

    //默認構造器,初始化一個長度爲0 的數組
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    //將集合c中全部元素轉化成數組的形式,存儲與當前List中
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

    //將數組toCopyIn拷貝以一份到array中
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
}

4.add與set過程學習

    CopyOnWriteArrayList在新增元素時,會先進行加鎖操做,以保證寫操做的併發安全ui

//將新增數據加到數組末尾
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();    //解鎖
    }
}

//新增元素到指定位置
public void add(int index, E element) {
    final ReentrantLock lock = this.lock;    //獲取重入鎖
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        if (index > len || index < 0)    //判斷索引是否合法
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        //判斷插入的位置是不是數組末尾
        if (numMoved == 0)    
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            newElements = new Object[len + 1];
            //將原數組中0到index的元素複製到新數組的0到index位置
            System.arraycopy(elements, 0, newElements, 0, index);
            //將原數組的index以後的數據複製到新數組的index+1位置以後
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        newElements[index] = element;    //新增element
        setArray(newElements);
    } finally {
        lock.unlock();
    }
}

//修改index索引上的數據
public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);


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

5.get的過程this

    CopyOnWriteArrayList的讀取過程並沒加鎖,這是由於CopyOnWriteArrayList的讀過程是自然線程安全的,全部的寫操做都是在一塊新的內存空間上,而讀操做則是在原有的空間上進行的,不會出現併發問題,讀寫自然分離。線程

public E get(int index) {
    return get(getArray(), index);
}

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

6.remove的過程

    刪除的過程其實以新增修改同樣,都是新建數組實現的。

//根據索引刪除數據
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();
    }
}

//將o從數組中刪除
public boolean remove(Object o) {
    Object[] snapshot = getArray();
    int index = indexOf(o, snapshot, 0, snapshot.length);    //獲取o的索引位置
    
    //判斷index是否合法
    return (index < 0) ? false : remove(o, snapshot, index);
}

//將snapshot中index位置的數據o刪除
private boolean remove(Object o, Object[] snapshot, int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();    //獲取當前數組
        int len = current.length;
        
        //判斷數組是否發生改變,便是否有其餘線程將數組修改了
        if (snapshot != current) findIndex: {
            //比較修改後的數組長度與index索引的大小,取小的值
            int prefix = Math.min(index, len);

            //遍歷current數組查找第一個與快照數組元素不一樣的索引
            for (int i = 0; i < prefix; i++) {
                if (current[i] != snapshot[i] && eq(o, current[i])) {
                    index = i;
                    break findIndex;
                }
            }

            //o元素索引大於等於current數組的長度
            //說明current數組中不存在o元素
            if (index >= len)
                return false;

            //o元素的索引仍在current數組中
            //且要刪除元素的索引的沒有發生改變
            if (current[index] == o)
                break findIndex;

            //o元素索引起生改變,從新獲取o元素的索引
            index = indexOf(o, current, index, len);
            if (index < 0)
                return false;
        }
        Object[] newElements = new Object[len - 1];
        System.arraycopy(current, 0, newElements, 0, index);
        System.arraycopy(current, index + 1,
                         newElements, index,
                         len - index - 1);
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

7.總結

    由上面的分析可得出CopyOnWriteArrayList具備以下特色:

    1.CopyOnWriteArrayList是線程安全的容器,讀寫分離,寫數據時經過ReentrantLock鎖定一個線程拷貝數組元素進行增刪改操做;讀數據時則不須要同步控制。

    2.CopyOnWriteArrayList中是容許null數據和重複數據的

    3.CopyOnWriteArrayList的併發安全性是經過建立新的數組和重入鎖實現的,會耗費大量內存空間,只適合讀多謝寫少,數據量不大的環境。

 

2、CopyOnWriteArraySet併發容器

1.CopyOnWriteArraySet

    因爲CopyOnWriteArraySet的底層是經過CopyOnWriteArrayList來實現的,其特色與CopyOnWriteArrayList,所以再也不作過多的分析。

相關文章
相關標籤/搜索