CopyOnWriteArrayList是一個ArrayList線程安全的變體。當數組內容有所變化時,拷貝一份新的出來,在新對象上進行修改操作,完成后把新對象引用賦值給array屬性。每發生一次改變,就需要復制一份數據,這樣復制是需要一定開銷的,所以CopyOnWriteArrayList適合讀操作遠大于修改操作的情況中。
CopyOnWriteArrayList構造函數:
public CopyOnWriteArrayList()
//Collection做初始化參數
public CopyOnWriteArrayList(Collection<? extends E> c)
//Array做初始化參數
public CopyOnWriteArrayList(E[] toCopyIn)
1.讀操作
privateE get(Object[] a, intindex) {
return(E) a[index];
}
publicE get(intindex) {
returnget(getArray(), index);
}
直接讀取,不需要加鎖,因為即使讀取過程中有其他線程改動List,也是開辟新的數組并在新數組上改動,舊數組對象始終是可用的。
2.寫操作
在CopyOnWriteArrayList中寫操作過程大致是這樣的。在原有數組的基礎上拷貝一份新的數組(容器副本),將改動操作在新數組上完成(即把新增元素加入新數組中),然后再把新數組對象的引用賦給CopyOnWriteArrayList的array。顯然,在多線程環境中,為了保證線程安全,整個過程需要加鎖。所以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,即數組尾部新增一個元素,同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;
//將新數組引用賦值給array
setArray(newElements);
} finally {
//釋放鎖
lock.unlock();
}
}
除了add方法,還有remove、removeRange、addIfAbsent等其他修改操作原理都是一樣的,都是新new一個數組對象,在新array上進行修改操作,完事后再將新數組引用賦值給實例變量array,當然修改操作都是需要加鎖的。
通過Iterator迭代器遍歷CopyOnWriteArrayList
通過Iterator遍歷CopyOnWriteArrayList的時候,不允許對array進行修改。remove、add、set方法直接拋出UnsupportedOperationException異常,這點是和普通list不同的地方。
線程安全性
我們可以看到,CopyOnWriteArrayList內部的array數組對象從被創建,到這個對象生命結束,是不可變的。變的是array變量的引用值,每做一次修改操作,array變量就指向新生成的數組對象,原對象被gc,如此反復。這種方式核心思想是減少鎖競爭,從而提高高并發時的讀取性能,但一定程度上犧牲了寫的性能。
由此可得:“寫入時復制(Copy-On-Write)”容器的線程安全性在于:只有正確地發布一個事實不可變的對象,那么在訪問該對象時就不需要做同步操作。這也就解釋了為什么通過迭代器Iterator是不允許進行修改操作的了。
優點
讀操作無需加鎖,并發環境性能不錯,但只適用于讀操作遠大于寫操作的場景。
缺陷
- 缺少同步控制,數據的一致性沒法保證。在并發環境中,一個線程在修改array的時候,其他線程是可以進行讀操作的,只是讀取的array任然是舊數據。所以對數據實時一致性要求高的場景,只能另尋它法。
- 每次修改都需要進行復制操作,如果list中存放的大對象,則復制時會產生兩個對象,會對內存產生一定壓力。所以大對象謹慎使用CopyOnWriteArrayList。
CopyOnWriteArraySet
CopyOnWriteArraySet完全基于CopyOnWriteArrayList實現。部分源碼如下:
public class CopyOnWriteArraySet<E> extends AbstractSet<E>{
//內部維護了一個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);
}
//其所有操作都是代理給內部的CopyOnWriteArrayList對象執行。
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();
}
。。。
}