美國眾議院不知道雅虎現任 CEO 是誰,只好將警告信發給了 4 年前的總裁
CopyOnWriteArraylist
CopyOnWrite容器
定義:寫時複製容器,當我們往一個容器新增元素的時候,不直接往當前容器新增,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裡新增元素,新增完元素之後,再將原容器的引用指向新的容器。
1.CopyOnWriteArrayList的定義
CopyOnWriteArrayList 是jdk1.5以後併發包中提供的一種併發容器,寫操作通過建立底層陣列的新副本來實現,是一種讀寫分離的併發策略,我們也成為“寫時複製容器”,為了將讀取的效能發揮到極致,CopyOnWriteArrayList 讀取是完全不用加鎖的,並且更厲害的是:寫入也不會阻塞讀取操作。只有寫入和寫入之間需要進行同步等待。
2.為什麼使用CopyOnWriteArraylist
集合框架中的ArrayList 是非執行緒安全的,Vector雖然是執行緒安全的,但是處理方式簡單粗暴(synchronized),效能較差。而CopyOnWriteArrayList提供了不同的處理併發的思路。
3.什麼時候使用CopyOnWriteArraylist
很多時候,我們系統中處理的都是讀多寫少的併發場景。CopyOnWriterArrayList 允許併發的讀,讀操作是無鎖的,效能較高。寫操作的話,比如向容器增加一個元素,則首先將當前容器複製一份,然後在新副本上執行寫操作,結束之後再將原容器的引用指向新容器。
4.優點:
讀操作效能很高,因為無需任何同步措施,比較適用於讀多寫少的併發場景。Java的List在遍歷時,若中途有別的執行緒對List容器進行修改,則會丟擲ConcurrentModificationException異常。而CopyOnWriteArrayList由於其"讀寫分離"的思想,遍歷和修改操作分別作用在不同的List容器,所以在使用迭代器進行遍歷時候,也就不會丟擲ConcurrentModificationException異常了。
5.缺點:
1、記憶體佔用問題。因為CopyOnWrite的寫時複製機制,所以在進行寫操作的時候,記憶體裡會同時駐紮兩個物件的記憶體,舊的物件和新寫入的物件。
2、資料一致性問題。CopyOnWrite容器只能保證資料的最終一致性,不能保證資料的實時一致性。所以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。當執行add或remove操作沒完成時,get獲取的仍然是舊陣列的元素。
6.原始碼分析:
在使用CopyOnWriteArrayList之前,我們先閱讀其原始碼瞭解下它是如何實現的。以下程式碼是向CopyOnWriteArrayList中add方法的實現(向CopyOnWriteArrayList裡新增元素),可以發現在新增的時候是需要加鎖的,否則多執行緒寫的時候會Copy出N個副本出來。【以下原始碼基於jdk1.8】
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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();
}
}
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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();
}
}