介紹
java併發包中的併發List只有CopyOnWriteArrayList,CopyOnWriteArrayList的修改操作底層都是通過拷貝一份數組進行的。使用ReentrantLock獨佔鎖來保證多線程併發下只有一個線程進行修改操作,下面我們通過源碼分析來逐步瞭解。
初始化方法
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
無參構造器中,創建了一個長度爲1Object數組.
添加元素
從上圖可知CopyOnWriteArrayList的添加元素方法有7個,我們着重講下add(E) 這個方法,其他方法的實現原理大同小異。
/**
* Appends the specified element to the end of this list.
* 翻譯:在列表的最後添加元素
* @param e element to be appended to this list
* 翻譯:添加到列表的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//1. 獲取重入鎖(獨佔鎖)
final ReentrantLock lock = this.lock;
lock.lock();
try {
//2. 獲取array
Object[] elements = getArray();
int len = elements.length;
//3. 複製原array到新array,並添加元素
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//4. 將新array替換原array
setArray(newElements);
return true;
} finally {
//5. 釋放重入鎖
lock.unlock();
}
}
從1 和 5 可以看出 的方法,通過獨佔鎖的機制,保證多線程併發修改元素時,只能有一個線程進行,其他線程會阻塞掛起,從而確保了此方法的原子性。
2 、3 處 通過複製原array到新array中,通過對新array進行添加元素操作,此處可知CopyOnWriteArrayList是一個無界的隊列。4 處 將 新數組替換了原數組
刪除元素
CopyOnWriteArrayList 有6個刪除方法,我們着重講解下remove(int)這個方法。
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left
* Returns the element that was removed from the list.
* 翻譯:刪除列表中指定位置的元素,往左邊移動隨後的元素,返回從列表中刪除的元素。
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//1. 獲取鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//2. 獲取原array
Object[] elements = getArray();
//3. 獲取要刪除的元素
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//4. 通過兩次複製,來刪除元素並整合新的array
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//5. 新array替換原array
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
獲取指定位置元素
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
在如上代碼中,當線程threadA 執行get(int index ),是分兩個步驟:1. 獲取數組 2. 通過數組下標index索引獲取指定元素,它會存在數據弱一致性的問題。當線程threadB 在 線程 threadA執行步驟1後和步驟2之前時,刪除了threadA 要訪問的元素(假設爲x元素),那麼threadA執行步驟2時獲取的是x元素,而不是其他的。因爲threadA在執行步驟1後,操作的是原數組。
總結
CopyOnWriteArrayList通過寫時複製來保證數據的一致性,而它獲取、修改、寫入這三個操作並不是原子性,所以通過獨佔鎖來保證任何一時刻只能有一個線程對數據進行操作。另外CopyOnWriteArraySet的實現原理和CopyOnWriteArrayList也是類似的。