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。