jdk原始碼分析之CopyOnWriteArrayList
CopyOnWriteArrayList的原理
CopyOnWriteArrayList的核心思想是利用高併發往往是讀多寫少的特性,對讀操作不加鎖,對寫操作,先複製一份新的陣列,在新的陣列上面修改,然後將新陣列賦值給舊陣列的引用,並通過volatile 保證其可見性,通過Lock保證併發寫。
底層資料結構
private volatile transient Object[] array;
final Object[] getArray() {
return array;
final void setArray(Object[] a) {
array = a;
}
底層採用Object陣列儲存資料
陣列使用volatile修飾保證可見性,不讀快取直接讀寫記憶體
陣列使用private修飾限制訪問與,只能通過getter和setter訪問
陣列使用transient修飾,表示序列化時忽略此欄位(自己定製序列化操作)
定製序列化操作
因為Object陣列被transient修飾,因此需要CopyOnWriteArrayList類自己制定序列化方案
方法writeObject和readObject處理物件的序列化。如果宣告該方法,它將會被ObjectOutputStream呼叫而不是採用預設的序列化方案。ObjectOutputStream使用了反射來尋找是否宣告瞭這兩個方法。因為ObjectOutputStream使用getPrivateMethod,所以這些方法不得不被宣告為priate以至於供ObjectOutputStream來使用。
在兩個方法的開始處,呼叫了defaultWriteObject()和defaultReadObject()。它們做的是預設的序列化程式,就像寫/讀所有的non-transient和 non-static欄位(但他們不會去做serialVersionUID的檢查).通常說來,所有我們想要自己處理的欄位都應該宣告為transient。這樣的話,defaultWriteObject/defaultReadObject便可以專注於其餘欄位,而我們則可為被transient修飾的欄位定製序列化。
/**
* Saves the state of the list to a stream (that is, serializes it).
*/
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);
}
先呼叫s.defaultWriteObject()對非transient修飾的欄位進行序列化操作
然後序列化寫入陣列的長度,再迴圈寫入陣列的元素
/**
* Reconstitutes the list from a stream (that is, deserializes it).
*/
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);
}
反序列化的時候先呼叫s.defaultReadObject()恢復沒有被transient修飾的欄位
然後為反序列化得到的CopyOnWriteArrayList物件建立一把新鎖
接著恢復陣列的長度,根據陣列的長度建立一個Object的陣列
然後恢復陣列的每一個元素
讀操作不加鎖
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
讀操作是直接通過getArray方法獲取Object陣列,然後通過下標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();
}
}
set方法先通過lock加鎖,然後獲取index位置的舊資料,供最後方法返回使用
E oldValue = get(elements, index);
接著建立陣列的副本,在副本上進行資料的替換
Object[] newElements = Arrays.copyOf(elements, len);
Arrays.copyOf(elements, len)方法將會從elements陣列複製len個資料建立一個新的陣列返回
然後在新陣列上進行資料替換,然後將新陣列設定為CopyOnWriteArrayList的底層陣列
newElements[index] = element;
setArray(newElements);
最後在finally塊裡邊釋放鎖
特定位置新增資料
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();
}
}
新增資料和替換資料類似,先加鎖,然後陣列下標檢查,接著建立陣列副本,在副本里邊新增資料,將副本設定為CopyOnWriteArrayList的底層陣列
相關文章
- 死磕 java集合之CopyOnWriteArrayList原始碼分析Java原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- Java集合乾貨——CopyOnWriteArrayList原始碼分析Java原始碼
- JDK1.8原始碼分析之HashMapJDK原始碼HashMap
- 【JDK】JDK原始碼分析-ReentrantLockJDK原始碼ReentrantLock
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(3)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(2)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(1)JDK原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- JDK原始碼分析-TreeSetJDK原始碼
- 【追光者系列】HikariCP原始碼分析之ConcurrentBag&J.U.C SynchronousQueue、CopyOnWriteArrayList原始碼
- JDK中的BitMap實現之BitSet原始碼分析JDK原始碼
- JDK 1.6 HashMap 原始碼分析JDKHashMap原始碼
- JDK原始碼分析(四)——LinkedHashMapJDK原始碼HashMap
- JDK1.8原始碼分析03之idea搭建原始碼閱讀環境JDK原始碼Idea
- 原始碼分析–ArrayList(JDK1.8)原始碼JDK
- 原始碼分析–HashSet(JDK1.8)原始碼JDK
- ArrayList原始碼分析 jdk1.8原始碼JDK
- JDK 原始碼分析(1) Object類JDK原始碼Object
- HashMap原始碼分析(JDK8)HashMap原始碼JDK
- LinkedList原始碼分析(jdk1.8)原始碼JDK
- JDK1.8 hashMap原始碼分析JDKHashMap原始碼
- HashMap原始碼分析 JDK1.8HashMap原始碼JDK
- ConcurrentHashMap原始碼分析-JDK18HashMap原始碼JDK
- ArrayList原始碼分析(JDK1.8)原始碼JDK
- 原始碼分析系列1:HashMap原始碼分析(基於JDK1.8)原始碼HashMapJDK
- 併發程式設計之 ConcurrentHashMap(JDK 1.8) putVal 原始碼分析程式設計HashMapJDK原始碼
- CopyOnWriteArrayList原始碼閱讀筆記原始碼筆記
- JDK原始碼解析系列之objectJDK原始碼Object
- JDK1.8 原始碼分析(九)--LinkedHashMapJDK原始碼HashMap
- JDK1.8 原始碼分析(十) -- TreeMapJDK原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- JDK1.8原始碼分析01之學習建議(可以延伸其他原始碼學習)JDK原始碼