什么是CopyOnWrite容器
【1】CopyOnWrite容器是基于并發模式Copy-on-Write模式(最簡單的并發解決方案)實作的用于避免共享的資料集合,
【2】CopyOnWrite容器又被成為寫時復制的容器,即當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,復制出一個新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的參考指向新的容器,這樣做的好處是我們可以對CopyOnWrite容器進行并發的讀,而不需要加鎖,因為當前容器不會添加任何元素,所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器,
【3】適用場景:讀多寫少的場景,
原始碼分析CopyOnWriteArrayList的實作
【1】屬性說明
//用于鎖住所有變化情況 final transient ReentrantLock lock = new ReentrantLock(); //存盤資料的陣列只能通過getArray/setArray進行改變 private transient volatile Object[] array;
【2】方法決議(僅展示部分方法)
1)添加方法
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]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
2)設定方法
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 { // 這里其實是將副本,重新放回去 setArray(elements); } return oldValue; } finally { lock.unlock(); } }
3)洗掉方法
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(); } }
4)獲取方法
private E get(Object[] a, int index) { return (E) a[index]; } public E get(int index) { return get(getArray(), index); } //final修飾方法之后該方法無法被子類覆寫 final Object[] getArray() { return array; }
【3】匯總說明
1.CopyOnWriteArrayList之所以選擇陣列而不是鏈表作為變數的存盤空間的原因:
1)提高處理速度,因為陣列存盤在記憶體中一塊連續的空間,而鏈表則是分散的,采用Arrays.copyOf 本質上底層還是使用 System.arraycopy 將那塊連續的記憶體空間的資料一次性拷貝,減少操作次數,
2.由原始碼可以看到,每次進行修改的時候都會加鎖僅限于一個執行緒進行變更操作,避免了共享變數并發寫的問題,所以是執行緒安全的,
3.但是其占用記憶體空間容易出現問題,如:在進行寫操作的時候,記憶體里會同時駐扎兩個物件的記憶體,舊的物件和新寫入的物件(注意:在復制的時候只是復制容器里的參考,只是在寫的時候會創建新物件添加到新容器里,而舊容器的物件還在使用,所以有兩份物件記憶體),如果這些物件占用的記憶體比較大,比如說200M左右,那么再寫入100M資料進去,記憶體就會占用300M,那么這個時候很有可能造成頻繁的Yong GC和Full GC,而Full GC過長則應用回應時間也隨之變長,
4.資料一致性問題,我們可以看出資料并不是實時一致性的,而是最終一致性,因為會先將資料拷貝到newElements 中,再設定到array的指標指向,要知道作業系統是基于時間片輪轉機制分配運行時間(如:時間耗盡沒有新的時間片給予,會導致執行緒背景關系切換),所以中間的間隔時間可以假設很長,那么修改是寫入了,但是變更還沒進行,其次,在加鎖的時間內,其他執行緒讀取的其實都是沒有修改的資料,
原始碼分析CopyOnWriteArraySet的實作
【1】屬性說明
private final CopyOnWriteArrayList<E> al;
【2】方法說明
public boolean add(E e) { return al.addIfAbsent(e); } //CopyOnWriteArrayList類的方法 public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } private static int indexOf(Object o, Object[] elements, int index, int fence) { if (o == null) { for (int i = index; i < fence; i++) if (elements[i] == null) return i; } else { for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i; } return -1; } private boolean addIfAbsent(E e, Object[] snapshot) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && eq(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
【3】匯總說明
1.CopyOnWriteArraySet的實作嚴格來說是基于CopyOnWriteArrayList進行實作的,去重邏輯在add中體現,
2.其次是效率問題:每次插入都需要去遍歷CopyOnWriteArrayList陣列一次,
3.雖然也是執行緒安全的,但是CopyOnWriteArrayList的缺點全部都會繼承,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/519020.html
標籤:Java
