CopyOnWriteArrayList 是一個(gè)線(xiàn)程安全的 ArrayList,通過(guò)內(nèi)部的 volatile 數(shù)組和顯式鎖 ReentrantLock 來(lái)實(shí)現(xiàn)線(xiàn)程安全。而 CopyOnWriteArraySet 是線(xiàn)程安全的 Set,它是由 CopyOnWriteArrayList 實(shí)現(xiàn),內(nèi)部持有一個(gè) CopyOnWriteArrayList 引用,所有的操作都是由 CopyOnWriteArrayList 來(lái)實(shí)現(xiàn)的,區(qū)別就是 CopyOnWriteArraySet 是無(wú)序的,并且不允許存放重復(fù)值。由于是一個(gè)Set,所以也不支持隨機(jī)索引元素。本章我們重點(diǎn)介紹 CopyOnWriteArrayList。
和 ArrayList 或 Set 相比,CopyOnWriteArrayList / CopyOnWriteArraySet 擁有以下特性:
- 適合元素比較少,并且讀取操作高于更新(add/set/remove)操作的場(chǎng)景
- 由于每次更新需要復(fù)制內(nèi)部數(shù)組,所以更新操作開(kāi)銷(xiāo)比較大
- 內(nèi)部的迭代器 iterator 使用了“快照”技術(shù),存儲(chǔ)了內(nèi)部數(shù)組快照, 所以它的 iterator 不支持remove、set、add操作,但是通過(guò)迭代器進(jìn)行并發(fā)讀取時(shí)效率很高。
源碼解析
核心參數(shù)
//鎖
final transient ReentrantLock lock = new ReentrantLock();
//用于存儲(chǔ)元素的內(nèi)部數(shù)組
private transient volatile Object[] array;
CopyOnWriteArrayList 實(shí)現(xiàn)非常簡(jiǎn)單。內(nèi)部使用了一個(gè) volatile 數(shù)組(array)來(lái)存儲(chǔ)數(shù)據(jù),保證了多線(xiàn)程環(huán)境下的可見(jiàn)性。在更新數(shù)據(jù)時(shí),都會(huì)新建一個(gè)數(shù)組,并將更新后的數(shù)據(jù)拷貝到新建的數(shù)組中,最后再將該數(shù)組賦值給 array。正由于這個(gè)原因,涉及到數(shù)據(jù)更新的操作效率很低。
由于 CopyOnWriteArrayList 源碼比較簡(jiǎn)單,內(nèi)部都是對(duì)數(shù)組的操作,所以咱們這里以add方法為例,其他方法就不一一分析了。
add(int index, E element)
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;
//計(jì)算偏移量
int numMoved = len - index;
if (numMoved == 0)
//作為add(E)處理
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
//調(diào)用native方法根據(jù)index拷貝原數(shù)組的前半段
System.arraycopy(elements, 0, newElements, 0, index);
//拷貝后半段
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
//System中arrayCopy的實(shí)現(xiàn)
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
說(shuō)明: 還是那句話(huà),非常簡(jiǎn)單,通過(guò)add()方法就可以看出整個(gè) CopyOnWriteArrayList 的實(shí)現(xiàn)就是操作內(nèi)部數(shù)組。首先通過(guò)lock加鎖,新建一個(gè)原數(shù)組長(zhǎng)度加1的新數(shù)組,將原數(shù)組(array)的數(shù)據(jù)拷貝到新數(shù)組中,如果給定索引(index)不是原數(shù)組最后一個(gè)索引,就分兩部分拷貝, 然后將給定元素放到新數(shù)組中給定索引處;最后,將新數(shù)組賦值給array。
小結(jié)
在整個(gè)java.util.concurrent框架里,這兩兄弟可以說(shuō)是最簡(jiǎn)單的兩個(gè)類(lèi)了。操作直觀,沒(méi)有復(fù)雜的運(yùn)算邏輯。本章重點(diǎn):CopyOnWriteArrayList 是通過(guò)拷貝數(shù)組來(lái)實(shí)現(xiàn)內(nèi)部元素操作的。