JAVA併發容器:CopyOnWriteArrayList與CopyOnWriteArraySet
阿新 • • 發佈:2018-12-09
生活
所有的程式設計師都劇作家,而所有計算機都是糟糕的演員。
CopyOnWriteArrayList介紹
還記得學集合的時候,學的第一個集合就是ArrayList.它是一個由陣列實現的集合。因為他對陣列的增刪改和查詢都是不加鎖的,所以它並不是執行緒安全的。
因此,我們會引入到一個執行緒安全的集合,Vector,他的底層也是陣列,與ArrayList不同的是,Vector裡對元素的增刪改查都是加了synchronized,因此說他是執行緒安全的。但是這種在任何場景都加鎖的集合顯然效率不高。
因此在多執行緒高併發環境下,引入了今天要研究的CopyOnWriteArrayList,他是一個寫時複製的一個集合,具體來說,它的底層還是一個數組,在查詢的時候直接查這個數組裡的元素,不需要加鎖,但是在做增刪改操作時,會獲取到互斥鎖,並且copy原陣列的資料到新陣列【在新陣列內進行增刪改操作】,執行完這些操作後,把集合陣列引用指向新陣列,因此在增刪改下不會對查詢產生影響,但是因為在做增刪改操作時每次都需要複製新陣列,非常耗費效能,因此這個集合適用在多讀少寫的場景,比如快取。
注意:如果你希望能馬上讀到剛寫進去的資料,就不適用這個集合了。
成員
//互斥鎖
transient final ReentrantLock lock = new ReentrantLock();
//容器陣列
private volatile transient Object[] array;
CopyOnWriteArrayList方法原始碼解析
簡單的看了一下CopyOnWriteArrayList的原始碼,非常簡單而且通俗易懂,這裡就貼一下增加資料和查詢資料的操作,主要理解一個解鎖,一個不加鎖,以及寫時複製的實現。
新增元素: 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(); } }
查詢
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
//可以看到確實不加鎖
public E get(int index) {
return get(getArray(), index);
}
CopyOnWriteArraySet
CopyOnWriteArraySet 是HashSet的併發實現。底層就是使用CopyOnWriteArrayList,要知道Set是不包含重複元素的,因此可以看到它底層實際新增元素的方法是呼叫了CopyOnWriteArrayList
addIfAbsent
public boolean addIfAbsent(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// Copy while checking if already present.
// This wins in the most common case where it is not present
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = new Object[len + 1];
for (int i = 0; i < len; ++i) {
//如果有相同元素,直接放棄新增
if (eq(e, elements[i]))
return false; // exit, throwing away copy
else
newElements[i] = elements[i];
}
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}