一、前言
由於Deque與Queue有很大的相似性,Deque為雙端隊列,隊列頭部和尾部都可以進行入隊列和出隊列的操作,所以不再介紹Deque,感興趣的讀者可以自行閱讀源碼,相信偶了Queue源碼的分析經驗,Deque的分析也會水到渠成,下面介紹List在JUC下的CopyOnWriteArrayList類,CopyOnWriteArrayList是ArrayList 的一個線程安全的變體,其中所有可變操作(add、set 等等)都是通過對底層數組進行一次新的復制來實現的。
二、CopyOnWriteArrayList數據結構
通過源碼分析可知,CopyOnWriteArrayList使用的數據結構是數組。結構如下
說明:CopyOnWriteArrayList底層使用數組來存放元素。
三、CopyOnWriteArrayList源碼分析
3.1 類的繼承關系
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
說明:CopyOnWriteArrayList實現了List接口,List接口定義了對列表的基本操作;同時實現了RandomAccess接口,表示可以隨機訪問(數組具有隨機訪問的特性);同時實現了Cloneable接口,表示可克隆;同時也實現了Serializable接口,表示可被序列化。
3.2 類的內部類
1. COWIterator類

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; } }
說明: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); } } }
說明:屬性中有一個可重入鎖,用來保證線程安全訪問,還有一個Object類型的數組,用來存放具體的元素。當然,也使用到了反射機制和CAS來保證原子性的修改lock域。
3.4 類的構造函數
1. CopyOnWriteArrayList()型構造函數

public CopyOnWriteArrayList() { // 設置數組 setArray(new Object[0]); }
說明:該構造函數用於創建一個空列表。
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); }
說明:該構造函數用於創建一個按 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)); }
說明:該構造函數用於創建一個保存給定數組的副本的列表。
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; }
說明:該函數用於復制指定的數組,截取或用 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(); } }
說明:此函數用於將指定元素添加到此列表的尾部,處理流程如下
① 獲取鎖(保證多線程的安全訪問),獲取當前的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(); } }
說明:該函數用於添加元素(如果數組中不存在,則添加;否則,不添加,直接返回)。可以保證多線程環境下不會重復添加元素,該函數的流程如下
① 獲取鎖,獲取當前數組為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(); } }
說明:此函數用於用指定的元素替代此列表指定位置上的元素,也是基於數組的復制來實現的。
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(); } }
說明:此函數用於移除此列表指定位置上的元素。處理流程如下
① 獲取鎖,獲取數組elements,數組長度為length,獲取索引的值elements[index],計算需要移動的元素個數(length - index - 1),若個數為0,則表示移除的是數組的最后一個元素,復制elements數組,復制長度為length-1,然后設置數組,進入步驟③;否則,進入步驟②
② 先復制index索引前的元素,再復制index索引后的元素,然后設置數組。
③ 釋放鎖,返回舊值。
四、示例
下面通過一個示例來了解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() + " "); } } }
運行結果(某一次)
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中添加元素也不會拋出異常。
五、總結
CopyOnWriteArrayList的源碼很簡單,其主要用到的快照的思路,使得在迭代的過程中,只是Object數組之前的某個快照,而不是最新的Object,這樣可以保證在迭代的過程中不會拋出ConcurrentModificationException異常。謝謝各位園友的觀看~