通俗易懂的 JUC 源碼剖析-Copy On Write Array List
作者:小強(qiáng)大人
來源:SegmentFault 思否社區(qū)
前言
實(shí)現(xiàn)原理
/**?The?lock?protecting?all?mutators?*/
final?transient?ReentrantLock?lock?=?new?ReentrantLock();
/**?The?array,?accessed?only?via?getArray/setArray.?*/
private?transient?volatile?Object[]?array;
下面直接進(jìn)入主題,看看它的add()方法如何實(shí)現(xiàn)的:
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();
????}
}
第一步,獲取數(shù)組的排他鎖
第二步,獲取數(shù)組元素和長度n,拷貝一個(gè)n+1長度的新數(shù)組
第三步,把待添加的元素e放在最后一個(gè)位置
第四步,覆蓋舊的數(shù)組,返回true表示添加成功
第五步,釋放鎖
簡而言之,它的實(shí)現(xiàn)思路就跟它的命名一樣,CopyOnWrite,“寫時(shí)復(fù)制”,添加元素的時(shí)候,先復(fù)制數(shù)組,添加完成后覆蓋掉舊的數(shù)組,這些步驟是在加鎖的環(huán)境完成的,也就是說這個(gè)過程中不會(huì)有其他線程同時(shí)也在寫數(shù)組,這就保證了寫操作的線程安全。
public?E?set(int?index,?E?element)?{
????final?ReentrantLock?lock?=?this.lock;
????lock.lock();
????try?{
????????Object[]?elements?=?getArray();
????????E?oldValue?=?get(elements,?index);
????????//?如果新值與舊值不同,則拷貝一個(gè)新數(shù)組,并在index處設(shè)置新值
????????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
????????????//?新值與舊值相同,為了保證volatile語義,也覆蓋下數(shù)組,即使內(nèi)容相同。
????????????setArray(elements);
????????}
????????return?oldValue;
????}?finally?{
????????lock.unlock();
????}
}
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;
????????//?如果刪除的是最后一個(gè)元素
????????if?(numMoved?==?0)
????????????setArray(Arrays.copyOf(elements,?len?-?1));
????????else?{
????????????Object[]?newElements?=?new?Object[len?-?1];
????????????//?分兩步復(fù)制
????????????System.arraycopy(elements,?0,?newElements,?0,?index);
????????????System.arraycopy(elements,?index?+?1,?newElements,?index,
????????????numMoved);
????????????setArray(newElements);
????????}
????????return?oldValue;
???}?finally?{
????????lock.unlock();
???}
}
public?E?get(int?index)?{
????return?get(getArray(),?index);
}
final?Object[]?getArray()?{
????return?array;
}
private?E?get(Object[]?a,?int?index)?{
????return?(E)?a[index];
}
上面提到,add()是先拷貝原數(shù)組,然后在拷貝的數(shù)組上操作的,在setArray()之前對原數(shù)組并沒有影響,因此讀的時(shí)候不需要加鎖。雖然不需要加鎖,但會(huì)出現(xiàn)數(shù)據(jù)弱一致性問題,下圖說明


它的迭代器iterator也是有這種弱一致性的特性,迭代對象是數(shù)組的快照,迭代過程中,如果其他線程修改了數(shù)組,對迭代器來說是不可見的。
代碼如下:
public?Iterator?iterator()?{
????return?new?COWIterator(getArray(),?0);
}
//?COW?=?Copy?On?Write
static?final?class?COWIterator?implements?ListIterator ?{
????/**?Snapshot?of?the?array?*/
????//?數(shù)組元素的一份快照
????private?final?Object[]?snapshot;
????/**?Index?of?element?to?be?returned?by?subsequent?call?to?next.??*/
????//?當(dāng)前迭代的位置-光標(biāo)
????private?int?cursor;
????private?COWIterator(Object[]?elements,?int?initialCursor)?{
????????cursor?=?initialCursor;
????????snapshot?=?elements;
???}
????public?boolean?hasNext()?{
????????return?cursor?????}
????public?boolean?hasPrevious()?{
????????return?cursor?>?0;
????}
????@SuppressWarnings("unchecked")
????public?E?next()?{
????????if?(!?hasNext())
????????????throw?new?NoSuchElementException();
????????return?(E)?snapshot[cursor++];
????}
?}

評論
圖片
表情
