Java高併發18-併發列表CopyOnWriteArrayList源碼解析

一、CopyOnWriteArrayList

  • 概覽:該List是一個JUC包中的唯一併發List,它是線程安全的,底層是一個數組,我們所有的操作都是使用了寫時複製的策略,下面這張圖片就是該類的一個類圖 18.1

1.類圖基本解釋

  • 有一個獨佔鎖ReentrantLock用於鎖定線程,同一時間只能由一個線程進行修改。

2.初始化

  • 首先看無參構造函數,創建一個大小爲0的數組
    private transient volatile Object[] array;

 public CopyOnWriteArrayListAnalysis() {
  setArray(new Object[0]);
 }
 
    final void setArray(Object[] a) {
        array = a;
    }
  • 然後看一下有參數的構造方法,傳入一個數組,就是一個該數組的副本的List數據結構
    public CopyOnWriteArrayListAnalysis(E[] toCopyIn) {
     //工具函數Arrays.copyOf表示複製一個第二參數的長度,內容爲第一個參數的數組,並且返回的數組類型是第三個參數
     setArray(Arrays.copyOf(toCopyIn, toCopyIn.length,Object[].class));
    }
  • 下面是如果傳入是一個Collection的情況,我們可以看到有參的構造方法,無論傳入什麼類型都會將其轉化爲Object[].class類型
    public CopyOnWriteArrayListAnalysis(Collection<? extends E> c) {
     Object[] elements;
     if(c.getClass() == CopyOnWriteArrayListAnalysis.class{
      elements = ((CopyOnWriteArrayListAnanlysis)c).getArray();
     }else {
      elements = c.toArray();
      //下面這條語句是用來判斷如果沒有返回一個Object[].class的情況
      if(elements.getClass() != Object[].class{
       elements = Arrays.copyOf(elements,elements.length,Object[].class);
      }
     }
    }
    
    final Object[] getArray() {
        return array;
    }

3.添加元素

  • 該類中有四種添加元素的方法,分別是add(E element),addIfAbsent(E element),add(int index,E element),addAllAbsent(Collection<? extends E> element)
  • 函數的釋義基本和List一致,只是底層的實現方式不同,下面我們就add(E elelment)方法爲例進行講解
    public boolean add(E element) {
     final ReentrantLock lock = this.lock; // 獲取該實例的獨佔鎖
     lock.lock();
     try {
      Object[] elements = getArray(); // 獲取內部存儲的數組,注意這時候還是原來的數組,
      // 只不過是使用一個新的引用來指向它的地址
      int len = elements.length; // 獲取數組的長度
      Object[] newElements = Arrays.copyOf(elements,len+1);
      // 使用Arrays工具類,創建一個新的數組,將前面的元素全都複製進去,並且留出一個位置
      newElements[len] = element;
      // 使用這個新數組來代替原來的數組
      setArray(newElements);
     }finally {
      lock.unlock();
     }
    }

注意點:(1)由於使用了獨佔鎖,所以同一時間只能由一個線程來進行add操作,保證了原子性;(2)add操作是創建一個新的數組,不是在原來的數組上進行操作的 ,並且該List輸一個無界List

4.獲取指定位置的元素

    public E get(int index) {
     return get(getArray(),index);
    }
    
    public E get(Object[] a,int index) {
     return (E)a[index];
    }
  • 獲取某一個索引的值,由於沒有進行加鎖操作,所以如果有刪除動作的同時,獲取某一個位置的元素,會出現“弱一致性問題”,我們從下文也可以看出,由於刪除一個元素,也不是在原數組上進行,先有一個副本,然後刪除使底層數組變更爲新數組,而get操作則還是在老數組的基礎上進行的,所以會有不一致的問題。

5.修改指定元素

    public E set(int index ,E element) {
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
      Object[] a = getArray();
      E oldValue = a[index];
      if(oldValue != element) {
       int len = a.length;
       Object[] newElements = Arrays.copyOf(a, len);
       newElements[index] = element;
       setArray(newElements);
      }else {
       setArray(a);
      }
      
     }finally{
      lock.unlock();
     }
    }
  • 基本邏輯還是很清晰的有一點需要強調的是如果新的元素沒有變動的化,仍然會調用setArray()方法進行設置一下,這個是爲了保證volatile的語義。

6.刪除元素

  • 刪除元素的方法有boolean remove(int index),boolean remove(Object o)和boolean remove(Object o,Object[] snapshot,int index)等,它們的基本原理都是類似,下面我們講解一下第一個函數
    public E remove(int index) {
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
      Object[] elements = getArray();
      int len = elements.length;
      int removeRemain = len - (index + 1); // 這個整數代表要遷移的剩餘元素個數
      if(removeRemain == 0) {
       setArray(Arrays.copyOf(elements, len-1));//除了最後一個全部都copy過去
      }else {
       Object[] newElements = new Object[len-1];
       System.arraycopy(elements,0,newElements,0,index);
       System.arraycopy(elements,index+1,newElements,index,removeRemain);
       // 這裏我們學習一個函數System.arraycopy
       // 第一個參數代表從哪裏複製,第二個參數代表從第幾個索引開始
       // 第三個參數代表複製到哪個數組中,從第幾個開始,複製幾個到新數組
       setArray(newElements);
      }
     }finally {
      lock.unlock();
     }
    }

7.迭代器

    public Iterator<E> iterator(){
     return new COWIteratro<E>(getArray(),0);
    }
    
    static final class COWIterator<Eimplements ListIterator<E{
     // array的快照版本
     private final Object[] snapshot;
     
     private int cursor;
     
     private COWIterator(Object[] elements,int initialCursor) {
      cursor = initialCursor;
      snapshot = elements;
     }
     
     public boolean hasNext() {
      return cursor < snapshot.length;
     }
     
     public E next() {
      if(!hasNext()) {
       throw new NoSuchElementException();
      }
      return <E>snapshot[cursor++];
     } 
    }  
  • 從上面的代碼可以看出,迭代器雖然是引用傳遞,用的數組還是底層數組,但是我們別忘記刪除,添加等操作都是重新指向一個新的數組,因此這種迭代器是弱一致性,同時查詢和編輯是線程不安全的。

二、源碼:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章