1、CopyOnWriteArrayList併發容器java
1.CopyOnWriteArrayList的底層數據結構數組
CopyOnWriteArrayList的底層實現是數組,CopyOnWriteArrayList是ArrayList的一個線程安全的變體,其增刪改操做都是經過對底層數組的從新複製來實現的。這種容器內存開銷很大,可是在讀操做頻繁(查詢),寫操做(增刪改)極少的場合卻極用,而且每一次寫操做都是在一塊新的空間上進行的,不存在併發問題。安全
2.CopyOnWriteArrayList的繼承關係數據結構
CopyOnWriteArrayList繼承關係以下圖所示,List接口和Collection接口在學習ArrayList時已經分析過,不在多說。併發
3.重要屬性和構造方法dom
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //可重入鎖 final transient ReentrantLock lock = new ReentrantLock(); //底層實現,用於存放數據的數組 private transient volatile Object[] array; //獲取數組 final Object[] getArray() { return array; } //修改數組 final void setArray(Object[] a) { array = a; } //默認構造器,初始化一個長度爲0 的數組 public CopyOnWriteArrayList() { setArray(new Object[0]); } //將集合c中全部元素轉化成數組的形式,存儲與當前List中 public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } //將數組toCopyIn拷貝以一份到array中 public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } }
4.add與set過程學習
CopyOnWriteArrayList在新增元素時,會先進行加鎖操做,以保證寫操做的併發安全ui
//將新增數據加到數組末尾 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(); //解鎖 } } //新增元素到指定位置 public void add(int index, E element) { final ReentrantLock lock = this.lock; //獲取重入鎖 lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0) //判斷索引是否合法 throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); Object[] newElements; int numMoved = len - index; //判斷插入的位置是不是數組末尾 if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; //將原數組中0到index的元素複製到新數組的0到index位置 System.arraycopy(elements, 0, newElements, 0, index); //將原數組的index以後的數據複製到新數組的index+1位置以後 System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; //新增element setArray(newElements); } finally { lock.unlock(); } } //修改index索引上的數據 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(); } }
5.get的過程this
CopyOnWriteArrayList的讀取過程並沒加鎖,這是由於CopyOnWriteArrayList的讀過程是自然線程安全的,全部的寫操做都是在一塊新的內存空間上,而讀操做則是在原有的空間上進行的,不會出現併發問題,讀寫自然分離。線程
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
6.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(); } } //將o從數組中刪除 public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); //獲取o的索引位置 //判斷index是否合法 return (index < 0) ? false : remove(o, snapshot, index); } //將snapshot中index位置的數據o刪除 private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); //獲取當前數組 int len = current.length; //判斷數組是否發生改變,便是否有其餘線程將數組修改了 if (snapshot != current) findIndex: { //比較修改後的數組長度與index索引的大小,取小的值 int prefix = Math.min(index, len); //遍歷current數組查找第一個與快照數組元素不一樣的索引 for (int i = 0; i < prefix; i++) { if (current[i] != snapshot[i] && eq(o, current[i])) { index = i; break findIndex; } } //o元素索引大於等於current數組的長度 //說明current數組中不存在o元素 if (index >= len) return false; //o元素的索引仍在current數組中 //且要刪除元素的索引的沒有發生改變 if (current[index] == o) break findIndex; //o元素索引起生改變,從新獲取o元素的索引 index = indexOf(o, current, index, len); if (index < 0) return false; } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } finally { lock.unlock(); } }
7.總結
由上面的分析可得出CopyOnWriteArrayList具備以下特色:
1.CopyOnWriteArrayList是線程安全的容器,讀寫分離,寫數據時經過ReentrantLock鎖定一個線程拷貝數組元素進行增刪改操做;讀數據時則不須要同步控制。
2.CopyOnWriteArrayList中是容許null數據和重複數據的
3.CopyOnWriteArrayList的併發安全性是經過建立新的數組和重入鎖實現的,會耗費大量內存空間,只適合讀多謝寫少,數據量不大的環境。
2、CopyOnWriteArraySet併發容器
1.CopyOnWriteArraySet
因爲CopyOnWriteArraySet的底層是經過CopyOnWriteArrayList來實現的,其特色與CopyOnWriteArrayList,所以再也不作過多的分析。