【JUC】JDK1.8源碼分析之CopyOnWriteArrayList(六)

1、前言java

  因爲Deque與Queue有很大的類似性,Deque爲雙端隊列,隊列頭部和尾部均可以進行入隊列和出隊列的操做,因此再也不介紹Deque,感興趣的讀者能夠自行閱讀源碼,相信偶了Queue源碼的分析經驗,Deque的分析也會水到渠成,下面介紹List在JUC下的CopyOnWriteArrayList類,CopyOnWriteArrayList是ArrayList 的一個線程安全的變體,其中全部可變操做(add、set 等等)都是經過對底層數組進行一次新的複製來實現的。數組

2、CopyOnWriteArrayList數據結構安全

  經過源碼分析可知,CopyOnWriteArrayList使用的數據結構是數組。結構以下數據結構

  說明:CopyOnWriteArrayList底層使用數組來存放元素。多線程

3、CopyOnWriteArrayList源碼分析dom

  3.1 類的繼承關係 ide

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

  說明:CopyOnWriteArrayList實現了List接口,List接口定義了對列表的基本操做;同時實現了RandomAccess接口,表示能夠隨機訪問(數組具備隨機訪問的特性);同時實現了Cloneable接口,表示可克隆;同時也實現了Serializable接口,表示可被序列化。函數

  3.2 類的內部類源碼分析

  1. COWIterator類  ui

static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        // 快照
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        // 遊標
        private int cursor;
        // 構造函數
        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
        // 是否還有下一項
        public boolean hasNext() {
            return cursor < snapshot.length;
        }
        // 是否有上一項
        public boolean hasPrevious() {
            return cursor > 0;
        }
        // next項
        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext()) // 不存在下一項,拋出異常
                throw new NoSuchElementException();
            // 返回下一項
            return (E) snapshot[cursor++];
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            if (! hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }
        
        // 下一項索引
        public int nextIndex() {
            return cursor;
        }
        
        // 上一項索引
        public int previousIndex() {
            return cursor-1;
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code remove}
         *         is not supported by this iterator.
         */
        // 不支持remove操做
        public void remove() {
            throw new UnsupportedOperationException();
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code set}
         *         is not supported by this iterator.
         */
        // 不支持set操做
        public void set(E e) {
            throw new UnsupportedOperationException();
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code add}
         *         is not supported by this iterator.
         */
        // 不支持add操做
        public void add(E e) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            Object[] elements = snapshot;
            final int size = elements.length;
            for (int i = cursor; i < size; i++) {
                @SuppressWarnings("unchecked") E e = (E) elements[i];
                action.accept(e);
            }
            cursor = size;
        }
    }
View Code

  說明:COWIterator表示迭代器,其也有一個Object類型的數組做爲CopyOnWriteArrayList數組的快照,這種快照風格的迭代器方法在建立迭代器時使用了對當時數組狀態的引用。此數組在迭代器的生存期內不會更改,所以不可能發生衝突,而且迭代器保證不會拋出 ConcurrentModificationException。建立迭代器之後,迭代器就不會反映列表的添加、移除或者更改。在迭代器上進行的元素更改操做(remove、set 和 add)不受支持。這些方法將拋出 UnsupportedOperationException。

  3.3 類的屬性  

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 版本序列號
    private static final long serialVersionUID = 8673264195747942595L;
    // 可重入鎖
    final transient ReentrantLock lock = new ReentrantLock();
    // 對象數組,用於存放元素
    private transient volatile Object[] array;
    // 反射機制
    private static final sun.misc.Unsafe UNSAFE;
    // lock域的內存偏移量
    private static final long lockOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = CopyOnWriteArrayList.class;
            lockOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("lock"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
View Code

  說明:屬性中有一個可重入鎖,用來保證線程安全訪問,還有一個Object類型的數組,用來存放具體的元素。固然,也使用到了反射機制和CAS來保證原子性的修改lock域。

  3.4 類的構造函數

  1. CopyOnWriteArrayList()型構造函數  

    public CopyOnWriteArrayList() {
        // 設置數組
        setArray(new Object[0]);
    }
View Code

  說明:該構造函數用於建立一個空列表。

  2. CopyOnWriteArrayList(Collection<? extends E>)型構造函數 

    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class) // 類型相同
            // 獲取c集合的數組
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else { // 類型不相同
            // 將c集合轉化爲數組並賦值給elements
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class) // elements類型不爲Object[]類型
                // 將elements數組轉化爲Object[]類型的數組
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        // 設置數組
        setArray(elements);
    }
View Code

  說明:該構造函數用於建立一個按 collection 的迭代器返回元素的順序包含指定 collection 元素的列表。該構造函數的處理流程以下

  ① 判斷傳入的集合c的類型是否爲CopyOnWriteArrayList類型,如果,則獲取該集合類型的底層數組(Object[]),而且設置當前CopyOnWriteArrayList的數組(Object[]數組),進入步驟③;不然,進入步驟②

  ② 將傳入的集合轉化爲數組elements,判斷elements的類型是否爲Object[]類型(toArray方法可能不會返回Object類型的數組),若不是,則將elements轉化爲Object類型的數組。進入步驟③

  ③ 設置當前CopyOnWriteArrayList的Object[]爲elements。

  3. CopyOnWriteArrayList(E[])型構造函數  

    public CopyOnWriteArrayList(E[] toCopyIn) {
        // 將toCopyIn轉化爲Object[]類型數組,而後設置當前數組
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
View Code

  說明:該構造函數用於建立一個保存給定數組的副本的列表。

  3.5 核心函數分析

  對於CopyOnWriteArrayList的函數分析,主要明白Arrays.copyOf方法便可理解CopyOnWriteArrayList其餘函數的意義。

  1. copyOf函數  

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        // 肯定copy的類型(將newType轉化爲Object類型,將Object[].class轉化爲Object類型,判斷二者是否相等,若相等,則生成指定長度的Object數組
        // 不然,生成指定長度的新類型的數組)
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        // 將original數組從下標0開始,複製長度爲(original.length和newLength的較小者),複製到copy數組中(也從下標0開始)
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
View Code

  說明:該函數用於複製指定的數組,截取或用 null 填充(若有必要),以使副本具備指定的長度。

  2. add函數  

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

  說明:此函數用於將指定元素添加到此列表的尾部,處理流程以下

  ① 獲取鎖(保證多線程的安全訪問),獲取當前的Object數組,獲取Object數組的長度爲length,進入步驟②。

  ② 根據Object數組複製一個長度爲length+1的Object數組爲newElements(此時,newElements[length]爲null),進入步驟③。

  ③ 將下標爲length的數組元素newElements[length]設置爲元素e,再設置當前Object[]爲newElements,釋放鎖,返回。這樣就完成了元素的添加。

  3. addIfAbsent 

    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])) // 當前數組的元素與快照的元素不相等而且e與當前元素相等
                        // 表示在snapshot與current之間修改了數組,而且設置了數組某一元素爲e,已經存在
                        // 返回
                        return false;
                if (indexOf(e, current, common, len) >= 0) // 在當前數組中找到e元素
                        // 返回
                        return false;
            }
            // 複製數組
            Object[] newElements = Arrays.copyOf(current, len + 1);
            // 對數組len索引的元素賦值爲e
            newElements[len] = e;
            // 設置數組
            setArray(newElements);
            return true;
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }
View Code

  說明:該函數用於添加元素(若是數組中不存在,則添加;不然,不添加,直接返回)。能夠保證多線程環境下不會重複添加元素,該函數的流程以下

  ① 獲取鎖,獲取當前數組爲current,current長度爲len,判斷數組以前的快照snapshot是否等於當前數組current,若不相等,則進入步驟②;不然,進入步驟④

  ② 不相等,表示在snapshot與current之間,對數組進行了修改(如進行了add、set、remove等操做),獲取長度(snapshot與current之間的較小者),對current進行遍歷操做,若遍歷過程發現snapshot與current的元素不相等而且current的元素與指定元素相等(可能進行了set操做),進入步驟⑤,不然,進入步驟③

  ③ 在當前數組中索引指定元素,若可以找到,進入步驟⑤,不然,進入步驟④

  ④ 複製當前數組current爲newElements,長度爲len+1,此時newElements[len]爲null。再設置newElements[len]爲指定元素e,再設置數組,進入步驟⑤

  ⑤ 釋放鎖,返回。

  4. set函數 

    public E set(int index, E element) {
        // 可重入鎖
        final ReentrantLock lock = this.lock;
        // 獲取鎖
        lock.lock();
        try {
            // 獲取數組
            Object[] elements = getArray();
            // 獲取index索引的元素
            E oldValue = get(elements, index);

            if (oldValue != element) { // 舊值等於element
                // 數組長度
                int len = elements.length;
                // 複製數組
                Object[] newElements = Arrays.copyOf(elements, len);
                // 從新賦值index索引的值
                newElements[index] = element;
                // 設置數組
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                // 設置數組
                setArray(elements);
            }
            // 返回舊值
            return oldValue;
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }
View Code

  說明:此函數用於用指定的元素替代此列表指定位置上的元素,也是基於數組的複製來實現的。

  5. 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) // 移動個數爲0
                // 複製後設置數組
                setArray(Arrays.copyOf(elements, len - 1));
            else { // 移動個數不爲0
                // 新生數組
                Object[] newElements = new Object[len - 1];
                // 複製index索引以前的元素
                System.arraycopy(elements, 0, newElements, 0, index);
                // 複製index索引以後的元素
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                // 設置索引
                setArray(newElements);
            }
            // 返回舊值
            return oldValue;
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }
View Code

  說明:此函數用於移除此列表指定位置上的元素。處理流程以下

  ① 獲取鎖,獲取數組elements,數組長度爲length,獲取索引的值elements[index],計算須要移動的元素個數(length - index - 1),若個數爲0,則表示移除的是數組的最後一個元素,複製elements數組,複製長度爲length-1,而後設置數組,進入步驟③;不然,進入步驟②

  ② 先複製index索引前的元素,再複製index索引後的元素,而後設置數組。

  ③ 釋放鎖,返回舊值。

4、示例

  下面經過一個示例來了解CopyOnWriteArrayList的使用

package com.hust.grid.leesf.collections;

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

class PutThread extends Thread {
    private CopyOnWriteArrayList<Integer> cowal;

    public PutThread(CopyOnWriteArrayList<Integer> cowal) {
        this.cowal = cowal;
    }

    public void run() {
        try {
            for (int i = 100; i < 110; i++) {
                cowal.add(i);
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> cowal = new CopyOnWriteArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            cowal.add(i);
        }
        PutThread p1 = new PutThread(cowal);
        p1.start();
        Iterator<Integer> iterator = cowal.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
        System.out.println();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        iterator = cowal.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
    }
}
View Code

  運行結果(某一次) 

0 1 2 3 4 5 6 7 8 9 100 
0 1 2 3 4 5 6 7 8 9 100 101 102 103 

  說明:在程序中,有一個PutThread線程會每隔50ms就向CopyOnWriteArrayList中添加一個元素,而且兩次使用了迭代器,迭代器輸出的內容都是生成迭代器時,CopyOnWriteArrayList的Object數組的快照的內容,在迭代的過程當中,往CopyOnWriteArrayList中添加元素也不會拋出異常。

5、總結

  CopyOnWriteArrayList的源碼很簡單,其主要用到的快照的思路,使得在迭代的過程當中,只是Object數組以前的某個快照,而不是最新的Object,這樣能夠保證在迭代的過程當中不會拋出ConcurrentModificationException異常。謝謝各位園友的觀看~

相關文章
相關標籤/搜索