Java集合乾貨——CopyOnWriteArrayList源碼分析

前言

CopyOnWriteArrayList是一個線程安全集合,原理簡單說就是:在保證線程安全的前提下,犧牲掉寫操做的效率來保證讀操做的高效。所謂CopyOnWrite就是經過複製的方式來完成對數據的修改,在進行修改的時候,複製一個新數組,在新數組上面進行修改操做,這樣就保證了不改變老數組,也就沒有一寫多讀數據不一致的問題了。java

具體的實現來看源碼,JDK 8。數據庫

CopyOnWriteArrayList

定義

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);
}
複製代碼

方法

add(E e)

添加一個新元素到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];
}
複製代碼

讀取的方法就很簡單了,按照下標獲取對應的元素。

CopyOnWriteArrayList總結
  1. 讀寫分離,咱們修改的是新數組,讀取的是老數組,不是一個對象,實現了讀寫分離。這種技術數據庫用的很是多,在高併發下爲了緩解數據庫的壓力,即便作了緩存也要對數據庫作讀寫分離,讀的時候使用讀庫,寫的時候使用寫庫,而後讀庫、寫庫之間進行必定的同步,這樣就避免同一個庫上讀、寫的IO操做太多。
  2. 場景:讀操做遠多於修改操做

我不能保證每個地方都是對的,可是能夠保證每一句話,每一行代碼都是通過推敲和斟酌的。但願每一篇文章背後都是本身追求純粹技術人生的態度。

永遠相信美好的事情即將發生。

相關文章
相關標籤/搜索