Java多線程系列之線程安全集合CopyOnWriteArrayList

1、CopyOnWriteArrayList介紹

    CopyOnWriteArrayList在Java中一般做爲ArrayList的線程安全實現,他繼承自List並實現了RandomAccess、Cloneable、java.io.Serializable在支持全部List操做的基礎上也支持隨機訪問、拷貝和序列化,功能與ArrayList基本相同。java

    CopyOnWriteArrayList底層存儲結構是一個動態對象數組,不一樣於ArrayList,內部維護了一個ReenTrantLock鎖lock,在每次進行寫操做前加鎖,採用了寫入時複製的思想,在每次對CopyOnWriteArrayList進行修改、刪除、添加等寫操做時都會事先在內部建立一個數組副本,在這個副本上進行寫操做,此時讀操做不須要加鎖,且讀取的仍然是CopyOnWriteArrayList內部對象數組。當CopyOnWriteArrayList讀取過程當中有其餘線程修改集合時讀取的數據多是舊數據,而且CopyOnWriteArrayList每次寫操做時都須要進行數組複製,在內存中須要佔用兩份數組對象內存,若是寫操做頻繁或者數組對象較大可能觸發頻繁的GC這無疑是十分低效的,所以CopyOnWrite應該只適用於讀操做遠高於寫操做,對數據實時性要求不高且數組對象較小的場合,例如緩存。數組

2、CopyOnWriteArrayList數據結構

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //序列號
    private static final long serialVersionUID = 8673264195747942595L;

    //互斥鎖,保證集合寫操做的線程安全
    final transient ReentrantLock lock = new ReentrantLock();

    //存儲集合元素的數組,只能經過set和get方法直接訪問
    private transient volatile Object[] array;
    
    private static final sun.misc.Unsafe UNSAFE;
    
    private static final long lockOffset;
}

3、CopyOnWriteArrayList源碼分析

1 - 構造函數

//無參構造函數,建立一個空的對象數組並讓內部動態數組引用array指向它
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    //構造函數入參是Collection容器對象
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        //若入參c是CopyOnWriteArrayList類實例那麼直接讓當前 
        //CopyOnWriteArrayList實例內部數組引用array指向入參c內部數組array
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        //若入參c不是CopyOnWriteArrayList實例那麼調用它的toArray方法返回包含內部全部元素的對象數組,
        //若是數組類型不是Object類型則還須要進行轉化
        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);
    }

    //入參爲數組的構造函數
    //將E[]數組轉化爲Object[]數組並讓內部對象數組引用指向新數組
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

2 - 添加元素

    咱們經過add(E e)方法瞭解一下CopyOnWriteArrayList如何實如今集合中添加元素。下面是add(E e)方法的源碼:緩存

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);
            //在新數組中插入指定新元素e
            newElements[len] = e;
            //讓集合對象數組引用指向建立的新對象數組
            setArray(newElements);
            return true;
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

    add(E e)方法在操做以前會先獲取當前集合對象的互斥鎖,進行加鎖,此時其餘線程沒法經過獲取該鎖修改該集合,分析源碼可知,CopyOnWriteArrayList添加新元素操做是在原數組的拷貝數組上作的,在拷貝數組上插入新元素e以後直接將集合內部的數組引用指向它,因爲內部數組array由volatile修飾所以是多線程可見的,元素添加操做未完成前其餘線程的讀操做不用阻塞讀取的是原對象數組,而在元素添加成功數組引用改變以後讀取的是最新的對象數組數據。

3 - 刪除元素

    CopyOnWriteArrayList刪除元素內部咱們經過分析remove(Object o)方法源碼來了解下,如下是remove(Object 0)方法源碼:安全

public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOf(o, snapshot, 0, snapshot.length);
        return (index < 0) ? false : remove(o, snapshot, index);
    }

    private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }

    前面的步驟沒什麼好說的,先獲取集合內部對象數組,遍歷它獲取指定元素o的數組下標,若在數組中不存在元素o則返回-1,最後若是返回的元素索引index小於0表示元素不存在刪除失敗,不然調用remove(o,snapshot,index)方法刪除元素,咱們進入該方法源碼:數據結構

private boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //獲取集合當前內部保存元素數組
            Object[] current = getArray();
            //獲取集合當前內部數組長度
            int len = current.length;
            //snapshot不等於current即在調用方法調用本方法以後到加鎖以前有其餘線程修改了集合
            if (snapshot != current) findIndex: {
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    //集合結構發生修改且元素未被刪除
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                //若待刪除元素索引index大於數組長度len,則該元素已經被其餘線程刪除,方法結束返回false
                if (index >= len)
                    return false;
                //獲取到被刪除元素o對應的數組下標直接退出最外層條件statement
                if (current[index] == o)
                    break findIndex;
                //遍歷內部數組下標index以後部分,獲取指定元素o的下標
                index = indexOf(o, current, index, len);
                //若獲取不到結束返回false
                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();
        }
    }

    由於remove(Object o, Object[] snapshot, int index)方法調用方remove(Object o)未作線程同步處理,因此在調用方調用本方法到加鎖以前可能有其餘線程修改集合結構,爲了保證集合中數據的最終一致性,本方法在內部同步代碼塊基於加鎖前傳入的內部元素數組和當前元素數組對比,判斷集合是否被修改過,若集合未被修改或者元素未被刪除,那麼建立新數組將當前內部對象數組剩餘元素數據複製到新數組,讓集合內部數組引用從新指向新數組。多線程

    這裏博主認爲靜態方法indexOf其實提供的是數組中獲取對應元素下標的功能,做爲CopyOnWriteArrayList類的私有靜態方法其實並不合適不符合程序設計思想,應該進行能力抽象單獨抽取出來放在一個類裏面。併發

4 - 設置元素

public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        //加鎖
        lock.lock();
        try {
            //獲取集合內部數組
            Object[] elements = getArray();
            //獲取對應位置元素oldValue
            E oldValue = get(elements, index);
            //指定元素element不等於舊元素oldValue
            if (oldValue != element) {
                //數組長度
                int len = elements.length;
                //建立原數組副本
                Object[] newElements = Arrays.copyOf(elements, len);
                //設置指定位置元素值爲指定元素element
                newElements[index] = element;
                //集合數組引用指向新數組
                setArray(newElements);
            }
            //指定索引位置舊元素等於新元素element 
            else {
                //數組引用仍指向原數組對象
                setArray(elements);
            }
            return oldValue;
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

5 - 其餘成員方法

//返回集合元素個數
    public int size() {
        return getArray().length;
    }

    //判斷集合是否爲空,是返回true
    public boolean isEmpty() {
        return size() == 0;
    }
    //判斷集合中是否包含元素o
    public boolean contains(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length) >= 0;
    }

    //查詢集合中第一個匹配元素o的索引下標
    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }

    //返回集合內部數組中下標index以後第一個匹配元素e的下標
    public int indexOf(E e, int index) {
        Object[] elements = getArray();
        return indexOf(e, elements, index, elements.length);
    }

    //返回集合中最後一個匹配元素o的索引下標
    public int lastIndexOf(Object o) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, elements.length - 1);
    }
    //拷貝函數
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
            CopyOnWriteArrayList<E> clone =
                (CopyOnWriteArrayList<E>) super.clone();
            clone.resetLock();
            return clone;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }

    //返回集合內部對象數組的一個副本
    public Object[] toArray() {
        Object[] elements = getArray();
        return Arrays.copyOf(elements, elements.length);
    }
    //相似將集合元素填充到一個數組中並返回,注意當入參數組a的容量小於集合元素個數時不會將元素數據拷貝到數組a中而是
    //直接返回方法內部建立的填充了集合元素數據的新數組
    public <T> T[] toArray(T a[]) {
        Object[] elements = getArray();
        int len = elements.length;
        if (a.length < len)
            return (T[]) Arrays.copyOf(elements, len, a.getClass());
        else {
            System.arraycopy(elements, 0, a, 0, len);
            if (a.length > len)
                a[len] = null;
            return a;
        }
    }
   
    //獲取集合索引index處元素
    public E get(int index) {
        return get(getArray(), index);
    }

    //在指定位置index處插入指定新元素element
    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];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
    //刪除集合中指定索引位置index處元素
    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();
        }
    }

    //插入指定元素e若是集合中已經存在該元素不插入,返回操做結果
    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

    //插入指定元素若是該元素在集合中已經存在操做失敗,該方法經過加鎖實現線程同步,經過方法參數snapshot(方法調用前
    //內部數組對象)與當前數組對象不相等肯定說明集合修改過
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

    //判斷當前集合是否包含指定容器c全部元素,是返回true否返回false
    public boolean containsAll(Collection<?> c) {
        Object[] elements = getArray();
        int len = elements.length;
        for (Object e : c) {
            if (indexOf(e, elements, 0, len) < 0)
                return false;
        }
        return true;
    }

    //刪除集合中容器c中的全部元素
    public boolean removeAll(Collection<?> c) {
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
                // temp array holds those elements we know we want to keep
                int newlen = 0;
                Object[] temp = new Object[len];
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i];
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
                if (newlen != len) {
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    //集合除了保留指定容器c中的全部元素,刪除其餘元素
    public boolean retainAll(Collection<?> c) {
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {
                // temp array holds those elements we know we want to keep
                int newlen = 0;
                Object[] temp = new Object[len];
                for (int i = 0; i < len; ++i) {
                    Object element = elements[i];
                    if (c.contains(element))
                        temp[newlen++] = element;
                }
                if (newlen != len) {
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    //往當前集合CopyOnWriteArrayList中插入指定容器c中的全部元素,原集合已經存在的元素則不重複插入
    public int addAllAbsent(Collection<? extends E> c) {
        Object[] cs = c.toArray();
        if (cs.length == 0)
            return 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            int added = 0;
            // uniquify and compact elements in cs
            for (int i = 0; i < cs.length; ++i) {
                Object e = cs[i];
                if (indexOf(e, elements, 0, len) < 0 &&
                    indexOf(e, cs, 0, added) < 0)
                    cs[added++] = e;
            }
            if (added > 0) {
                Object[] newElements = Arrays.copyOf(elements, len + added);
                System.arraycopy(cs, 0, newElements, len, added);
                setArray(newElements);
            }
            return added;
        } finally {
            lock.unlock();
        }
    }

    //清空集合中的全部元素,實質就是讓內部數組引用指向一個空數組對象
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            setArray(new Object[0]);
        } finally {
            lock.unlock();
        }
    }

6 - CopyOnWriteArrayList實現線程安全原理總結

    經過粗略分析了CopyOnWriteArrayList源碼咱們應該清楚做爲一個線程安全容器它實現線程安全是基於一個ReentrantLock互斥鎖lock和內部維護的一個volatile對象數組array。每次對集合進行修改都會對互斥鎖加鎖,防止其餘線程併發修改集合,每次集合修改操做都是先在新數組(即集合內部數組副本)上進行而後在修改完成以後重置數組引用到新數組,在這過程當中集合讀取的仍然是集合內部數組,所以集合修改操做不會阻塞集合讀取。

 

4、CopyOnWriteArrayListe使用示例

相關文章
相關標籤/搜索