線程安全的List:CopyOnWriteArrayList
并發(fā)包中的并發(fā)List只有CopyOnWriteArrayList让腹。CopyOnWriteArrayList是一個線程安全的ArrayList,對于CopyOnWriteArrayList的操作都是在底層的一個復制的數組上進行的仪或。另外,同在并發(fā)包中的CopyOnWriteArraySet的底層實現也是CopyOnWriteArrayList坏瞄。
增
add(E e)
? 從(1)處可以發(fā)現CopyOnWriteArrayList使用ReetrantLock作為線程安全的鎖工具墨吓,對于add操作打却,由于獨占鎖的特性填渠,同一時刻只允許一個線程對array進行修改操作弦聂。
? (2)處獲取數組快照,在add方法氛什,由于獨占鎖的存在莺葫,不會有其它線程對array進行修改操作,所以array就是List準確的底層數組枪眉。
? (3)處捺檬,根據獲取到的數組新建一個容量比原數組大一的新數組,并把新增元素放到新數組的最后贸铜。
從以上代碼可以得到CopyOnWriteArrayList的核心思想正如語義堡纬,在寫操作的時候,新建一個數組蒿秦,并把原數組的內容copy到新的數組中烤镐。
public boolean add(E e) {
// (1)ReentrantLock默認為非公平鎖,
// 作為獨占鎖同時只允許一個線程對lock()修飾的代碼塊進行操作
// (在本類中基本是對array的修改操作)
final ReentrantLock lock = this.lock;
lock.lock();
try {
// (2)獲取array快照
Object[] elements = getArray();
int len = elements.length;
// (3)拷貝array快照到新建數組棍鳖,新建數組的大小為原數組的size+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
// (4)釋放獨占鎖炮叶,可以競爭
lock.unlock();
}
}
// 獲取array快照
final Object[] getArray() {
return array;
}
addIfAbsent(E e)
這個方法也是CopyOnWriteArraySet的核心方法。
public boolean addIfAbsent(E e) {
// (0)這里獲取的是快照渡处,因為沒有加鎖镜悉,可能有其它線程有寫操作
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
// (1)檢查array是否已經變化,在之前的檢查時医瘫,有其它線程進行了寫操作
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
// (2)取小的那個長度侣肄,進行遍歷對比。
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
// (3) 遍歷對應時醇份,當某個位置元素不相等稼锅,且 current[i]與
// 當前需要添加的元素相等, 意味著其它線程新增該元素成功叮喳。
// 當前線程失敗。
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
// (4)在current中比快照數組長的部分找到目標元素缰贝,新增失敗
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
正如方法語義:缺少時插入馍悟,方法先查詢待新增元素在當前array中的索引是否大于0,如果大于0證明已經存在該元素剩晴,返回false锣咒,新增失敗。這里要注意的是addIfAbsent()分為兩步赞弥,首先在addIfAbsent(E e)方法中主要做的是檢查該元素是否已存在毅整,如果不存在就調用addIfAbsent(E e, Object[] snapshot)進行真正的新增操作。而傳入的snapshot在(0)處獲取绽左,由于檢查時并沒有進行加鎖操作悼嫉,這意味著snapshot正如其名 ,是快照拼窥,在當前線程A檢查的同時戏蔑,可能有其它線程B已經進行了新增操作將目標元素新增進List。
下圖展示了當在List[1,2,3,4]中新增5時鲁纠,出現代碼標記中(3)(4)情況的可能例子总棵。如果以下兩種情況都未發(fā)生,那么CopyOnWriteArrayList就要做它的老本行了改含,新建數組情龄,拷貝元素。
? 這里需要注意的是拷貝的是current數組而不是snapshot快照數組捍壤。出現(3)情況可能又有另一線程C刪除了元素4骤视。
刪 remove()
remove(int index) 索引刪
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)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
? 看過add的代碼专酗,remove(int index) 就代碼設計而言沒有太大差別,在這邊主要區(qū)分了兩種情況帜慢,①刪除的元素是中間元素笼裳;②刪除的元素是結尾元素。
獲取大小
public int size() {
return getArray().length;
}
在獲取size時躬柬,僅僅是獲取array快照,并獲取它的長度抽减。并非加鎖操作允青,所以這個size在并發(fā)情況下并不正確,只能在某種程度上反映這個list的大致大小卵沉。
弱一致性的迭代器
public ListIterator<E> listIterator() {
return new COWIterator<E>(getArray(), 0);
}
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;
}
...
}
可以看到當我們獲取CopyOnWriteArrayList的迭代器的時候颠锉,無鎖獲取array法牲,并把array和0作為構造函數入參傳給迭代器。顯然琼掠,這個迭代器同size()方法一樣拒垃,并不能準確的反應CopyOnWriteArrayList實時的準確情況。