CopyOnWriteArrayList
寫入時複製,只要正確發佈一個事實不可變對象,在訪問該對象時就不再需要進一步同步,在每次修改時,都會創建並重新發佈一個新的容器副本,從而實現可變性.
使用場景:迭代>修改,事件通知系統(註冊和註銷事件監聽器操作少於接收事件通知的操作)
//構造時,初始化容量爲0的Object數組
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
add
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
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();
}
}
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;
//最後一個數組索引,則直接copy
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//copy2次
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];
}
set
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
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
//保證最終一致性
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
總結
應用場景:讀多寫少
如:黑名單,監聽器
讀上沒加鎖,所以支持大量併發,但涉及修改的時候,有2個數組副本,當數組過大,則造成多餘的內存消耗.
CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。之所以只能保證最終一致,是因爲每次涉及到修改都會copy一個副本然後回寫,最終的結果是一致的,但是在copy途中如果有讀操作,那麼就會造成數據不一致問題.