ArrayList 是一個不安全的容器,在多線程調用 add 方法的時候會出現 ArrayIndexOutOfBoundsException 異常,而 Vector 雖然安全,但因爲其 add 方法和 get 方法都使用了 synchronized 關鍵字,致使在併發時的性能使人擔心,所以,偉大的 Doug Lea 編寫了 CopyOnWriteArrayList 併發容器,用於替代併發時的 ArrayList,而該類的類名叫 「寫的時候拷貝集合」。也很是符合他的設計,那麼,咱們就看看他是如何實現的。java
既然是 ArrayList ,第一個看的固然是 add 方法。數組
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();
}
}
複製代碼
該方法步驟以下:多線程
再看看 get 方法:併發
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
複製代碼
能夠看到該方法很是簡單:獲取到成員變量 array,根據下標獲取。沒有使用鎖,徹底支持併發。工具
從 add 方法和 get 方法中,咱們看出了做者的意圖,Doug Lea 認爲容器 get 的操做比 add 的操做頻繁,使用了相似讀寫分離的方式,讀讀操做徹底併發,而寫的時候,並不修改原有的內容,這對於保證當前在讀線程的數據一致性很是重要,而後對原有的數據進行一次複製,將改寫的內容寫入到副本中,寫完以後,再將修改完的副本替換原來的數據。這樣就能夠保證寫操做不會影響讀了。同時使用 volatile 變量,也保證了內存可見性,更新以後當即就能被其餘線程看到。性能
該類保證了併發時的安全,同時,相比於 Vector 性能要高出不少(讀讀徹底併發)。而 Vector 同時只能有一個線程進行讀寫,簡直可怕。this
但該類也不是完美的。該類的迭代器不支持 remove 操做,也不支持 set 操做,也不支持 add 操做。在迭代器中調用這 三個方法將拋出 UnsupportedOperationException 異常。spa
可是該類能夠在 for 循環中作刪除操做,這點和 ArrayList 也是不同的,由於該類每次刪除以後也都是 拷貝重寫賦值。而 ArrayList 使用 for 循環刪除實際上使用的迭代器的 next 方法,而迭代器每次都會檢查ArrayList 的狀態,調用 checkForComodification 方法,所以會拋出 ConcurrentModificationException 異常。ArrayList 必須使用迭代器的 remove 方法。線程
public void remove() {
throw new UnsupportedOperationException();
}
public void set(E e) {
throw new UnsupportedOperationException();
}
public void add(E e) {
throw new UnsupportedOperationException();
}
複製代碼
總的來講,併發環境下,強烈建議使用該類代替 ArrayList,該類的讀讀操做可保證徹底併發。支持 for 循環作刪除操做。不支持迭代器remove ,set, add。