摘要:僅僅當有多個線程同時進行寫操作時,才會進行同步。可以看到,上述方法返回一個迭代器對象,的迭代是在舊數組上進行的,當創建迭代器的那一刻就確定了,所以迭代過程中不會拋出并發修改異常。另外,迭代器對象也不支持修改方法,全部會拋出異常。
本文首發于一世流云專欄:https://segmentfault.com/blog...一、CopyOnWriteArrayList簡介
ArrayList是一種“列表”數據機構,其底層是通過數組來實現元素的隨機訪問。JDK1.5之前,如果想要在并發環境下使用“列表”,一般有以下3種方式:
使用Vector類
使用Collections.synchronizedList返回一個同步代理類;
自己實現ArrayList的子類,并進行同步/加鎖。
前兩種方式都相當于加了一把“全局鎖”,訪問任何方法都需要首先獲取鎖。第3種方式,需要自己實現,復雜度較高。
JDK1.5時,隨著J.U.C引入了一個新的集合工具類——CopyOnWriteArrayList:
大多數業務場景都是一種“讀多寫少”的情形,CopyOnWriteArrayList就是為適應這種場景而誕生的。
CopyOnWriteArrayList,運用了一種“寫時復制”的思想。通俗的理解就是當我們需要修改(增/刪/改)列表中的元素時,不直接進行修改,而是先將列表Copy,然后在新的副本上進行修改,修改完成之后,再將引用從原列表指向新列表。
這樣做的好處是讀/寫是不會沖突的,可以并發進行,讀操作還是在原列表,寫操作在新列表。僅僅當有多個線程同時進行寫操作時,才會進行同步。
二、CopyOnWriteArrayList原理 內部結構CopyOnWriteArrayList的字段很簡單:
public class CopyOnWriteArrayListimplements List , RandomAccess, Cloneable, java.io.Serializable { /** * 排它鎖, 用于同步修改操作 */ final transient ReentrantLock lock = new ReentrantLock(); /** * 內部數組 */ private transient volatile Object[] array; }
其中,lock用于對修改操作進行同步,array就是內部實際保存數據的數組。
構造器定義
CopyOnWriteArrayList提供了三種不同的構造器,這三種構造器最終都是創建一個數組,并通過setArray方法賦給array字段:
/** * 空構造器. */ public CopyOnWriteArrayList() { setArray(new Object[0]); } ? 僅僅是設置一個了大小為0的數組,并賦給字段array: final void setArray(Object[] a) { array = a; }
/** * 根據已有集合創建 */ 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); }
/** * 根據已有數組創建. * * @param toCopyIn the array (a copy of this array is used as the * internal array) * @throws NullPointerException if the specified array is null */ public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }核心方法
查詢——get方法
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
可以看到,get方法并沒有加鎖,直接返回了內部數組對應索引位置的值:array[index]
添加——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); // 內部array引用指向新數組 return true; } finally { lock.unlock(); } }
add方法首先會進行加鎖,保證只有一個線程能進行修改;然后會創建一個新數組(大小為n+1),并將原數組的值復制到新數組,新元素插入到新數組的最后;最后,將字段array指向新數組。
上圖中,ThreadB對Array的修改由于是在新數組上進行的,所以并不會對ThreadA的讀操作產生影響。
刪除——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) // index位置剛好是最后一個元素 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(); } }
刪除方法和插入一樣,都需要先加鎖(所有涉及修改元素的方法都需要先加鎖,寫-寫不能并發),然后構建新數組,復制舊數組元素至新數組,最后將array指向新數組。
其它統計方法
public int size() { return getArray().length; } public boolean isEmpty() { return size() == 0; }
迭代
CopyOnWriteArrayList對元素進行迭代時,僅僅返回一個當前內部數組的快照,也就是說,如果此時有其它線程正在修改元素,并不會在迭代中反映出來,因為修改都是在新數組中進行的。
public Iteratoriterator() { return new COWIterator (getArray(), 0); } ? static final class COWIterator implements ListIterator { /** * Snapshot of the array */ private final Object[] snapshot; /** * Index of element to be returned by subsequent call to next. */ private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } public E next() { if (!hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } // ... }
可以看到,上述iterator方法返回一個迭代器對象——COWIterator,COWIterator的迭代是在舊數組上進行的,當創建迭代器的那一刻就確定了,所以迭代過程中不會拋出并發修改異常——ConcurrentModificationException。
另外,迭代器對象也不支持修改方法,全部會拋出UnsupportedOperationException異常。
三、總結CopyOnWriteArrayList的思想和實現整體上還是比較簡單,它適用于處理“讀多寫少”的并發場景。通過上述對CopyOnWriteArrayList的分析,讀者也應該可以發現該類存在的一些問題:
1. 內存的使用
由于CopyOnWriteArrayList使用了“寫時復制”,所以在進行寫操作的時候,內存里會同時存在兩個array數組,如果數組內存占用的太大,那么可能會造成頻繁GC,所以CopyOnWriteArrayList并不適合大數據量的場景。
2. 數據一致性
CopyOnWriteArrayList只能保證數據的最終一致性,不能保證數據的實時一致性——讀操作讀到的數據只是一份快照。所以如果希望寫入的數據可以立刻被讀到,那CopyOnWriteArrayList并不適合。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76940.html
摘要:我們之前已經介紹過了,底層基于跳表實現,其操作平均時間復雜度均為。事實上,內部引用了一個對象,以組合方式,委托對象實現了所有功能。線程安全內存的使用較多迭代是對快照進行的,不會拋出,且迭代過程中不支持修改操作。 showImg(https://segmentfault.com/img/bVbggjf?w=600&h=377); 本文首發于一世流云專欄:https://segmentfa...
摘要:整個包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據一系列常見的多線程設計模式,設計了并發包,其中包下提供了一系列基礎的鎖工具,用以對等進行補充增強。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發于一世流云專欄:https...
摘要:我們來看下的類繼承圖可以看到,實現了接口,在多線程進階二五之框架中,我們提到過實現了接口,以提供和排序相關的功能,維持元素的有序性,所以就是一種為并發環境設計的有序工具類。唯一的區別是針對的僅僅是鍵值,針對鍵值對進行操作。 showImg(https://segmentfault.com/img/bVbggic?w=960&h=600); 本文首發于一世流云專欄:https://seg...
摘要:接口截止目前為止,我們介紹的阻塞隊列都是實現了接口。該類在構造時一般需要指定容量,如果不指定,則最大容量為。另外,由于內部通過來保證線程安全,所以的整體實現時比較簡單的。另外,雙端隊列相比普通隊列,主要是多了隊尾出隊元素隊首入隊元素的功能。 showImg(https://segmentfault.com/img/bVbgZ7j?w=770&h=514); 本文首發于一世流云專欄:ht...
摘要:在章節中,我們說過,維護了一把全局鎖,無論是出隊還是入隊,都共用這把鎖,這就導致任一時間點只有一個線程能夠執行。入隊鎖對應的是條件隊列,出隊鎖對應的是條件隊列,所以每入隊一個元素,應當立即去喚醒可能阻塞的其它入隊線程。 showImg(https://segmentfault.com/img/bVbgCD9?w=1920&h=1080); 本文首發于一世流云專欄:https://seg...
閱讀 2785·2021-10-14 09:42
閱讀 3608·2021-10-11 10:59
閱讀 2941·2019-08-30 11:25
閱讀 3074·2019-08-29 16:25
閱讀 3224·2019-08-26 17:40
閱讀 1225·2019-08-26 13:30
閱讀 1143·2019-08-26 11:46
閱讀 1329·2019-08-23 15:22