CopyOnWriteArrayList是一個線程安全集合,原理簡單說就是:在保證線程安全的前提下,犧牲掉寫操做的效率來保證讀操做的高效。所謂CopyOnWrite就是經過複製的方式來完成對數據的修改,在進行修改的時候,複製一個新數組,在新數組上面進行修改操做,這樣就保證了不改變老數組,也就沒有一寫多讀數據不一致的問題了。java
具體的實現來看源碼,JDK 8。數據庫
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 複製代碼
在定義上和ArrayList大差不差,不過多解釋,有興趣能夠看以前關於ArrayList的文章。數組
屬性緩存
一個是Lock,另外一個是一個對象數組。安全
/** The lock protecting all mutators */
//一把鎖
transient final ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
//一個對象數組,只從方法getArray/setArray處接受值
//volatile後面會有專門的文章來講明
private volatile transient Object[] array;
複製代碼
CopyOnWriteArrayList的初始化容量是0,分爲這樣的幾個步驟。併發
//在無參構造方法中會調用setArray方法,參數是一個空的對象數組,而後經過setArray把這個空的數組賦值給屬性array
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
複製代碼
須要說明的是另外一個有參構造方法,參數能夠是一個集合dom
//按照集合的迭代器返回的順序建立一個包含指定集合元素的列表
public CopyOnWriteArrayList(Collection<? extends E> c) {
//將集合轉爲數組
Object[] elements = c.toArray();
//elements不可以是一個空的對象數組 爲何要if這樣一個條件嘞 由於屬性中須要賦值的是一個對象數組 因此若是if成立執行的就是把原數組變爲一個對象數組 若是自己就是對象數組也就不用轉了
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
//賦值給屬性
setArray(elements);
}
複製代碼
添加一個新元素到list的尾部。高併發
public boolean add(E e) {
//鎖 1.5新版本的鎖 已經不用synchronized了
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
//getArray獲取屬性值 就是老數組
Object[] elements = getArray();
int len = elements.length;
//這裏是重點 在這裏 複製老數組獲得了一個長度+1的新數組
Object[] newElements = Arrays.copyOf(elements, len + 1);
//添加元素
newElements[len] = e;
//用新數組取代老數組
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
複製代碼
從add方法中咱們能夠看到所謂的CopyOnWrite是如何實現的,在須要修改的時候,複製一個新數組,在新數組上修改,修改結束取代老數組,這樣保證了修改操做不影響老數組的正常讀取,另修改操做是加鎖的,也就是說沒有了線程不安全的問題。this
和ArrayList相比較,效率比較低,只添加一個元素的狀況下(初始容量均爲0),用時是ArrayList的5倍左右,可是隨着CopyOnWriteArrayList中元素的增長,CopyOnWriteArrayList的修改代價將愈來愈昂貴。spa
除了添加其餘的修改操做也都是這樣的套路,不作過多解釋,如remove,也是加鎖,複製新數組。
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();
}
}
複製代碼
#####get
public E get(int index) {
return get(getArray(), index);
}
//按照下標獲取數組中對應的元素
private E get(Object[] a, int index) {
return (E) a[index];
}
複製代碼
讀取的方法就很簡單了,按照下標獲取對應的元素。
我不能保證每個地方都是對的,可是能夠保證每一句話,每一行代碼都是通過推敲和斟酌的。但願每一篇文章背後都是本身追求純粹技術人生的態度。
永遠相信美好的事情即將發生。