java.util.concurrent包中定義常見集合類對應的並發集合類,用於高效處理並發場景,其中CopyOnWriteArrayList對應就是ArrayList。顧名思義CopyOnWrite,寫時拷貝,這裡寫包括對集合類的修改操作,都會創建一個副本。
類的定義
public class CopyOnWriteArrayList可以看到沒有繼承任何子類,實現接口和ArrayList類似。implements List , RandomAccess, Cloneable, java.io.Serializable
關鍵屬性
/** The lock protecting all mutators */ transient final ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private volatile transient Object[] array;同樣是采用數組方式實現,多了一個volatile聲明,用於保證線程可見性。沒有size聲明表示實際包含元素的大小,多了一個ReentrantLock對象聲明。
常見方法
構造方法
public CopyOnWriteArrayList() { setArray(new Object[0]); //默認創建一個空數組 } public CopyOnWriteArrayList(Collection extends E> c) { Object[] 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); }size方法,直接返回數組大小,說明array數組只包含實際大小的空間
public int size() { return getArray().length; }get方法,和ArrayList中類似,不過沒有index的范圍判斷
public E get(int index) { return (E)(getArray()[index]); }add方法,可以看到無論是在尾部還是指定位置添加,都有鎖定和解鎖操作,在設置值之前都先將原先數組拷貝一份並擴容至size+1大小。
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);//拷貝array屬性,並擴展為length+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]; //elements[0,index) ---> newElements[0,index) System.arraycopy(elements, 0, newElements, 0, index); //elements[index,len) --> newElements[index+1,len+1) System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }set方法,ArrayList中set方法直接改變數組中對應的引用,這裡需要拷貝數組然後再設置。(else那個分支沒看懂,為什麼值沒有改變還需要設置來保證volatile寫語義)
public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); Object oldValue = 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 (E)oldValue; } finally { lock.unlock(); } }remove(int)方法,和指定位置添加類似,需要拷貝[0,index)和[index+1,len)之間的元素
public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object oldValue = elements[index]; int numMoved = len - index - 1;nt if (numMoved == 0) //刪除最後一個元素 setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; //elements[0,index) --> newElements[0,index) System.arraycopy(elements, 0, newElements, 0, index); //elements[index+1,len) --> newElements[index,len-1) System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return (E)oldValue; } finally { lock.unlock(); } }remove(Object)方法,分配一個len-1大小的新數組,遍歷原來數組,如果找到則將原來數組以後的元素拷貝到新數組中並將list設置為新數組,否則直接給新數組賦值上原來數組。
public boolean remove(Object o) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (len != 0) { // Copy while searching for element to remove // This wins in the normal case of element being present int newlen = len - 1; Object[] newElements = new Object[newlen]; for (int i = 0; i < newlen; ++i) { if (eq(o, elements[i])) { // found one; copy remaining and exit for (int k = i + 1; k < len; ++k) newElements[k-1] = elements[k]; setArray(newElements); return true; } else newElements[i] = elements[i]; } // special handling for last cell if (eq(o, elements[newlen])) { setArray(newElements); return true; } } return false; } finally { lock.unlock(); } }
COWIterator定義
/** 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; }iterator和listIterator中會傳遞當前數組的引用和cursor(無參方法為0,有參數方法為對應值)
常見方法
public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; }另外其他add、remove和set修改容器的方法都沒有實現,直接throw new UnsupportedOperationException();
1. CopyOnWriteArrayList的迭代器保留一個執行底層基礎數組的引用,這個數組當前位於迭代器的起始位置,由於基礎數組不會被修改(修改都是復制一個新的數組),因此對其同步只需要保證數組內容的可見性。多個線程可以同時對這個容器進行迭代,而不會彼此干擾或者與修改容器的線程互相干擾。不會拋出CocurrentModificationException,並且返回元素與創建迭代器創建時的元素完全一致,不必考慮之後修改操作帶來影響。
2. 每次修改容器都會復制底層數組,這需要一定開銷,特別是容器規模較大。僅當迭代操作遠遠多於修改操作時,才應該使用CopyOnWriteArrayList。