靈活運用ReentrantLock及volatile構造線程安全的CopyOnWriteArrayList


CopyOnWriteArrayList是一個線程安全且在讀操作時候無鎖的ArrayList,其具體實現如下:

首先在CopyOnWriteArrayList內部定義了一個private類型的數組,並提供getter setter方法,不過需要注意的是該對象數組是被volatile關鍵字修飾的(關於volatile關鍵字可以參考我的博客“關於volatile的使用”一文),和ArrayList不同的是這裏創建一個大小爲0的數組,源碼如下:

transient final ReentrantLock lock = new ReentrantLock();    

/** The array, accessed only via getArray/setArray. */
    private volatile transient Object[] array;

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

 

然後我們來看看其add,remove等方法的實現

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();
 }
}

 

這裏使用了ReentrantLock首先調用了lock方法,然後獲取到對象數組,再調用Arrays.copyOf(elements,len+1)複製了一個長度加1的數組,緊接着把新增元素e添加到數組最後

一個位置上,再調用setArray方法將array引用的對象置換成新的對象,最後再釋放鎖,整個新增操作就完成了,這裏有一個疑惑就是作者爲什麼在複製數組的時候並沒有選擇

System.arraycopy,而使用的是Arrays.copyOf方法呢。下面是其remove方法的實現

public boolean remove(Object o) {
 final ReentrantLock lock = this.lock;
 lock.lock();
 try {
     Object[] elements = getArray();
     int len = elements.length;
     if (len != 0) {
  // Copy while searching for element to remove
  // This wins in the normal case of element being present
  int newlen = len - 1;
  Object[] newElements = new Object[newlen];

  for (int i = 0; i < newlen; ++i) {
      if (eq(o, elements[i])) {
   // found one;  copy remaining and exit
   for (int k = i + 1; k < len; ++k)
       newElements[k-1] = elements[k];
   setArray(newElements);
   return true;
      } else
   newElements[i] = elements[i];
  }

  // special handling for last cell
  if (eq(o, elements[newlen])) {
      setArray(newElements);
      return true;
  }
     }
     return false;
 } finally {
     lock.unlock();
 }
    }

 

和add方法一樣,此處也是使用的ReentrantLock來保證線程安全的,首先創建比當前數組長度小1的數組,然後循環判斷如果相等就將數組後面元素前移,然後替換掉引用的對象。

 

public E get(int index) {
        return (E)(getArray()[index]);
    }


get方法就很直接了,沒有使用同步關鍵字和鎖,直接定位到數組指定位置。

最後我們來看看其迭代方法:

public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

直接用存放數據的Object數組構造一個COWIterator對象,下面是主要代碼:

private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

整個設計非常的巧妙,非常適合於讀多寫少的應用場景。而且相比直接使用同步關鍵字或鎖來說代價來得小,非常適用!

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