一、 核心思想:
CopyOnWriteArrayList的核心思想是利用高併發往往是讀多寫少的特性,對讀操作不加鎖,對寫操作,先複製一份新的集合,在新的集合上面修改,然後將新集合賦值給舊的引用,並通過volatile 保證其可見性,當然寫操作的鎖是必不可少的了。
二、類圖預覽:
方法基本分爲CopyOnWriteArrayList、indexOf、contains、get、set、add、remove、addIfAbsent和iterator幾類:
1、CopyOnWriteArrayList 構造方法:
基本使用Arrays.copyOf 方法,將參數的集合類設置到array屬性上。
2、indexOf方法:
簡單的通過循環,對比找到所在的位置,核心代碼:
- for (int i = index; i < fence; i++)
- if (o.equals(elements[i]))
- return i;
值得注意有兩點,一是支持NULL對象、二是lastIndexOf從後面往前,提高性能
3、 contains方法:
該方法使用indexOf方法,避免代碼重複。containsAll方法也是簡單的循環判斷是否包含單個元素。
4、get方法:
直接返回對應下標元素
5、set方法:
- public E set(int index, E element) {
- final ReentrantLock lock = this.lock;
- lock.lock();
- try {
- Object[] elements = getArray();
- E oldValue = get(elements, index);
- if (oldValue != element) {
- int len = elements.length;
- Object[] newElements = Arrays.copyOf(elements, len);
- newElements[index] = element;
- setArray(newElements);
- } else {
- // Not quite a no-op; ensures volatile write semantics
- setArray(elements);
- }
- return oldValue;
- } finally {
- lock.unlock();
- }
- }
可以看到該法使用ReentrantLock鎖, Arrays.copyOf創建一個新的數組是核心思想體現,oldValue != element這個判斷更是儘可能的提高性能的努力。
而在esle裏面,明明沒有任何修改,爲什麼還要條用set方法,並且在addAllAbsent 方法裏面有沒有使用,以及那句註釋(Not quite a no-op; ensures volatile write semantics),有幾封郵件討論這個問題。
大意是說:爲了確保 voliatile 的語義,任何一個讀操作都應該是寫操作的結構,所以儘管寫操作沒有改變數據,還是調用set方法,當然這僅僅是語義的說明,去掉也是可以的。而對於 addIfAbsent方法爲什麼沒有使用set方法,那是因爲該方法本身的語義就是寫或者不寫,不寫故不需要保持語義。
參考如下:
http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006886.html
http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006887.html
http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006888.html
http://en.usenet.digipedia.org/thread/13652/1242/
6、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();
- }
- }
同樣很簡單,遵循,使用鎖,Arrays.copyOf copy新數組、新增一個元素、set回去步驟。
另外一個重載的指定位置add元素的核心代碼如下:
- newElements = new Object[len + 1];
- System.arraycopy(elements, 0, newElements, 0, index);
- System.arraycopy(elements, index, newElements, index + 1, numMoved);
主要使用System.arraycopy方法copy到一個新的數組
7、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();
- }
- }
同樣很簡單,使用 System.arraycopy、Arrays.copyOf移動元素
移除指定元素方法的核心代碼:通過雙重循環,比較移動。
- 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];
移除指定集合內方法核心代碼:
- for (int i = 0; i < len; ++i) {
- Object element = elements[i];
- if (!c.contains(element))
- temp[newlen++] = element;
- }
- if (newlen != len) {
- setArray(Arrays.copyOf(temp, newlen));
- return true;
- }
8、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();
- }
- }
這裏可以看到沒有又相同的元素之間return了,沒有調用set方法;
9、retainAll 方法:
- Object[] temp = new Object[len];
- for (int i = 0; i < len; ++i) {
- Object element = elements[i];
- if (c.contains(element))
- temp[newlen++] = element;
- }
基本是removeAll的翻版,只是 if (c.contains(element)) 這個是否定罷了。
10、writeObject、readObject方法:
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException{
- s.defaultWriteObject();
- Object[] elements = getArray();
- // Write out array length
- s.writeInt(elements.length);
- // Write out all elements in the proper order.
- for (Object element : elements)
- s.writeObject(element);
- }
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- // bind to new lock
- resetLock();
- // Read in array length and allocate array
- int len = s.readInt();
- Object[] elements = new Object[len];
- // Read in all elements in the proper order.
- for (int i = 0; i < len; i++)
- elements[i] = s.readObject();
- setArray(elements);
- }
雖然CopyOnWriteArrayList 類實現了 序列化接口,但是變量數組確有transient關鍵字通過實現這兩個方法。將快照序列化
11、iterator 方法:
- public void remove() {
- throw new UnsupportedOperationException();
- }
針對iterator使用了一個叫COWIterator的閹割版迭代器,因爲不支持寫操作 ,如上面add、set、remove都會跑出異常,當獲取CopyOnWriteArrayList的迭代器時,是將迭代器裏的數據引用指向當前引用指向的數據對象,無論未來發生什麼寫操作,都不會再更改迭代器裏的數據對象引用,所以迭代器也很安全。
綜上:
在CopyOnWriteArrayList裏處理寫操作(包括add、remove、set等)是先將原始的數據通過Arrays.copyof()來生成一份新的數組,然後在新的數據對象上進行寫,寫完後再將原來的引用指向到當前這個數據對象,並且加鎖。
讀操作是在引用的當前對象上進行讀(包括get,iterator等),不存在加鎖和阻塞。
因爲每次使用CopyOnWriteArrayList.add都要引起數組拷貝, 所以應該避免在循環中使用CopyOnWriteArrayList.add。可以在初始化完成後設置到CopyOnWriteArrayList中,或者使用CopyOnWriteArrayList.addAll方法
CopyOnWriteArrayList採用“寫入時複製”策略,對容器的寫操作將導致的容器中基本數組的複製,性能開銷較大。所以在有寫操作的情況下,CopyOnWriteArrayList性能不佳,而且如果容器容量較大的話容易造成溢出。