CopyOnWriteArrayList是一個ArrayList線程安全的變體源祈。當數(shù)組內(nèi)容有所變化時吧趣,拷貝一份新的出來任内,在新對象上進行修改操作赞哗,完成后把新對象引用賦值給array屬性。每發(fā)生一次改變,就需要復制一份數(shù)據(jù)万伤,這樣復制是需要一定開銷的窒悔,所以CopyOnWriteArrayList適合讀操作遠大于修改操作的情況中。
CopyOnWriteArrayList構(gòu)造函數(shù):
public CopyOnWriteArrayList()
//Collection做初始化參數(shù)
public CopyOnWriteArrayList(Collection<? extends E> c)
//Array做初始化參數(shù)
public CopyOnWriteArrayList(E[] toCopyIn)
1.讀操作
privateE get(Object[] a, intindex) {
return(E) a[index];
}
publicE get(intindex) {
returnget(getArray(), index);
}
直接讀取壕翩,不需要加鎖,因為即使讀取過程中有其他線程改動List傅寡,也是開辟新的數(shù)組并在新數(shù)組上改動放妈,舊數(shù)組對象始終是可用的。
2.寫操作
在CopyOnWriteArrayList中寫操作過程大致是這樣的荐操。在原有數(shù)組的基礎(chǔ)上拷貝一份新的數(shù)組(容器副本)芜抒,將改動操作在新數(shù)組上完成(即把新增元素加入新數(shù)組中),然后再把新數(shù)組對象的引用賦給CopyOnWriteArrayList的array托启。顯然宅倒,在多線程環(huán)境中,為了保證線程安全屯耸,整個過程需要加鎖拐迁。所以CopyOnWriteArrayList的寫操作性能損耗是很大的,一方面需要競爭獲取鎖疗绣,另一方面需要進行復制操作线召。
下面以add(int index, E element)方法為例說明CopyOnWriteArrayList的修改操作:
//指定位置增加元素
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
//修改array前獲取鎖
lock.lock();
try {
//獲取原有array
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
//index=length,即數(shù)組尾部新增一個元素,同add(E element)
newElements = Arrays.copyOf(elements, len + 1);
else {
//兩次復制
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//新增元素
newElements[index] = element;
//將新數(shù)組引用賦值給array
setArray(newElements);
} finally {
//釋放鎖
lock.unlock();
}
}
除了add方法,還有remove多矮、removeRange缓淹、addIfAbsent等其他修改操作原理都是一樣的,都是新new一個數(shù)組對象塔逃,在新array上進行修改操作讯壶,完事后再將新數(shù)組引用賦值給實例變量array,當然修改操作都是需要加鎖的湾盗。
通過Iterator迭代器遍歷CopyOnWriteArrayList
通過Iterator遍歷CopyOnWriteArrayList的時候伏蚊,不允許對array進行修改。remove格粪、add丙挽、set方法直接拋出UnsupportedOperationException異常,這點是和普通list不同的地方匀借。
線程安全性
我們可以看到颜阐,CopyOnWriteArrayList內(nèi)部的array數(shù)組對象從被創(chuàng)建,到這個對象生命結(jié)束吓肋,是不可變的凳怨。變的是array變量的引用值,每做一次修改操作,array變量就指向新生成的數(shù)組對象肤舞,原對象被gc紫新,如此反復。這種方式核心思想是減少鎖競爭李剖,從而提高高并發(fā)時的讀取性能芒率,但一定程度上犧牲了寫的性能。
由此可得:“寫入時復制(Copy-On-Write)”容器的線程安全性在于:只有正確地發(fā)布一個事實不可變的對象篙顺,那么在訪問該對象時就不需要做同步操作偶芍。這也就解釋了為什么通過迭代器Iterator是不允許進行修改操作的了。
優(yōu)點
讀操作無需加鎖,并發(fā)環(huán)境性能不錯德玫,但只適用于讀操作遠大于寫操作的場景匪蟀。
缺陷
- 缺少同步控制,數(shù)據(jù)的一致性沒法保證宰僧。在并發(fā)環(huán)境中材彪,一個線程在修改array的時候,其他線程是可以進行讀操作的琴儿,只是讀取的array任然是舊數(shù)據(jù)段化。所以對數(shù)據(jù)實時一致性要求高的場景,只能另尋它法造成。
- 每次修改都需要進行復制操作穗泵,如果list中存放的大對象,則復制時會產(chǎn)生兩個對象谜疤,會對內(nèi)存產(chǎn)生一定壓力佃延。所以大對象謹慎使用CopyOnWriteArrayList。
CopyOnWriteArraySet
CopyOnWriteArraySet完全基于CopyOnWriteArrayList實現(xiàn)夷磕。部分源碼如下:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>{
//內(nèi)部維護了一個CopyOnWriteArrayList對象
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
public CopyOnWriteArraySet(Collection<? extends E> c) {
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
//其所有操作都是代理給內(nèi)部的CopyOnWriteArrayList對象執(zhí)行履肃。
public boolean add(E e) {
return al.addIfAbsent(e);
}
public boolean remove(Object o) {
return al.remove(o);
}
public Iterator<E> iterator() {
return al.iterator();
}
public Object[] toArray() {
return al.toArray();
}
。坐桩。尺棋。
}