CopyOnWriteArrayList 源碼閱讀與分析
CopyOnWriteArrayList是併發包下的一個線程安全、可以實現高併發的ArrayList
首先來看看它的構造方法:
final void setArray(Object[] a) {
array = a;
}
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
可以看到,它和ArrayList有一定的不同,它是創建一個大小爲 0 的數組。ArrayList是一個空數組。
下面來看看它的add(E) 方法,進一步瞭解它的實現原理
顧名思義,它的名字叫CopyOnWriteArrayList(),也就是在進行寫操作時,如add,remove時,會進行復制操作,即創建一個新的比當前數長度大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);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
add 方法並沒有使用 synchronized 關鍵字來實現互斥,而是通過使用ReentrantLock 來進行加鎖操作。可以看到,它的實現比較簡單,首先加鎖,然後創建一個比目前數組長度大1的新數組,再把舊數組中的值複製到新的數組中去。然後再調用setArray方法改變引用,完成add的操作,結束以後,再釋放鎖。
之所以再每次的修改數組操作(add/remove等)之後,會新建一個數組,修改完畢之後,再將原來的引用指向新的數組。是爲了保證數組被一個線程遍歷時,沒有其他線程對數組進行修改。所以也就不會拋出ArrayList併發情況下出現的ConcurrentModificationException錯誤。
下面再來看看get(int) 方法
先來看看代碼的實現:
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
可以看到,非常簡單,直接獲取當前數組對應的位置的元素,但是由於方法沒有進行任何的加鎖操作,所以可能會出現讀到髒數據的現象,這樣可以有比較高的性能,所以在修改少,讀取多的場景下。它開始很好的選擇。
再來看看remove(E) 方法
和 add 方法一樣,此方法也通過ReetrantLock 來保證其線程安全。
首先判斷要刪除的元素是不是最後一個元素:
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));
如果不是最後一個元素,就進行兩次複製,先把原數組從0到index-1的數據複製到新的數組,再把index+1到最後一個元素複製到新的數組中,最後再改變引用。完成這些以後,就完成了刪除的操作。
可以看到CopyOnWriteArrayList沒有像ArrayList一樣的動態擴容機制,而是再每次對數組修改時都會新建一個新的數組,爲了避免ConcurrentModificationException異常,但是這樣也會對性能有一定的影響。而且它並沒有對讀操作進行任何的線程安全的操作,所以可能會出現讀到髒數據的情況。所以當寫操作比較多時,使用CopyOnWriteArrayList個人感覺不是好的選擇。