ArrayList是開發中使用比較多的集合,它不是線程安全的,CopyOnWriteArrayList就是線程安全版本的ArrayList。CopyOnWriteArrayList一樣是經過數組實現,這個類的名字叫「CopyOnWrite 」,它是在寫入的時候拷貝數組,對副本進行操做。html
CopyOnWriteArrayList採用了一種讀寫分離的併發策略。CopyOnWriteArrayList容器容許併發讀,讀操做是無鎖的,性能較高。至於寫操做,好比向容器中添加一個元素,則首先將當前容器複製一份,而後在新副本上執行寫操做,結束以後再將原容器的引用指向新容器。示意圖以下:java
經過類圖,能夠看到CopyOnWriteArrayList的繼承體系·:數組
實現了List, RandomAccess, Cloneable, java.io.Serializable等接口。安全
實現了List,提供了基礎的添加、刪除、遍歷等操做。併發
實現了RandomAccess,提供了隨機訪問的能力。dom
實現了Cloneable,能夠被克隆。源碼分析
實現了Serializable,能夠被序列化。性能
//可重入鎖,保證線程安全 final transient ReentrantLock lock = new ReentrantLock(); //存放數據元素的數組,只能經過get/set方法訪問 private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; }
public CopyOnWriteArrayList() { setArray(new Object[0]); }
public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; // 若是c也是CopyOnWriteArrayList類型 // 那麼直接把它的數組拿過來使用 if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { //不然,先轉換爲數組 elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) // 檢查c.toArray()返回的是否是Object[]類型,若是不是,從新拷貝成Object[].class類型 if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }
//把toCopyIn的元素拷貝給當前list的數組。 public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
添加一個元素到末尾this
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(); } }
在指定位置插入數組.net
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) // 若是插入的位置是最後一位 // 那麼拷貝一個n+1的數組, 其前n個元素與舊數組一致 newElements = Arrays.copyOf(elements, len + 1); else { // 若是插入的位置不是最後一位 // 那麼新建一個n+1的數組 newElements = new Object[len + 1]; //拷貝舊數組[0,……index-1]下標的元素 System.arraycopy(elements, 0, newElements, 0, index); //拷貝舊數組的其他元素到新數組[index+1,……length+1],恰好空出了index下標位置 System.arraycopy(elements, index, newElements, index + 1, numMoved); } //將插入的元素放到index下標位置 newElements[index] = element; //給array賦值 setArray(newElements); } finally { //釋放鎖 lock.unlock(); } }
寫入操做:
根據下標位置移除數據元素:
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) // 若是移除的是最後一位 // 那麼直接拷貝一份n-1的新數組, 最後一位就自動刪除了 setArray(Arrays.copyOf(elements, len - 1)); else { // 若是移除的不是最後一位 // 那麼新建一個n-1的新數組 Object[] newElements = new Object[len - 1]; // 將前index個元素拷貝到新數組中 System.arraycopy(elements, 0, newElements, 0, index); // 將index後面(不包含)的元素往前挪一位 // 這樣正好把index位置覆蓋掉了, 至關於刪除了 System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { //釋放鎖 lock.unlock(); } }
刪除操做:刪除操做同理,將除要刪除元素以外的其餘元素拷貝到新副本中,而後切換引用,將原容器引用指向新副本。同屬寫操做,須要加鎖。
public E get(int index) { return get(getArray(), index); } final Object[] getArray() { return array; } private E get(Object[] a, int index) { return (E) a[index]; }
獲取操做:獲取操做屬於讀操做,直接經過數組下標獲取數據元素,沒有加鎖,因此保證了性能。
public int size() { //返回數組長度 return getArray().length; }
和ArrayList不一樣,查看ArrayList源碼閱讀筆記,能夠發現ArrayList中是有size屬性的,這是由於ArrayList數組的長度實際是要大於集合的大小的。CopyOnWriteArrayList每次修改都是拷貝一份正好能夠存儲目標個數元素的數組,因此不須要size屬性,直接返回數組長度便可。
CopyOnWriteArrayList使用ReentrantLock重入鎖加鎖,保證線程安全;
CopyOnWriteArrayList的寫操做都要先拷貝一份新數組,在新數組中作修改,修改完了再用新數組替換老數組,因此空間複雜度是O(n),性能相對低下;
CopyOnWriteArrayList的讀操做支持隨機訪問,時間複雜度爲O(1);
CopyOnWriteArrayList採用讀寫分離的思想,讀操做不加鎖,寫操做加鎖,且寫操做佔用較大內存空間,因此適用於讀多寫少的場合;
CopyOnWriteArrayList只保證最終一致性,不保證明時一致性;
紙上得來終覺淺,絕知此事要躬行。
參考:
【1】:【死磕 Java 集合】— CopyOnWriteArrayList源碼分析
【2】:CopyOnWriteArrayList實現原理及源碼分析