Java基础之CopyOnWriteArrayList
前言
今天我們來學習一下CopyOnWriteArrayList這個比ArrayList更安全的集合,同時帶著以下幾個問題去分析源碼。
- CopyOnWriteArrayList如何保證線程安全?
- CopyOnWriteArrayList有什么優缺點嗎?
- CopyOnWriteArrayList與ArrayList有什么區別嗎?
- 了解CopyOnWriteArraySet嗎?
繼承關系
public class CopyOnWriteArrayList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable {//......} 復制代碼首先我們來看一下CopyOnWriteArrayList的繼承關系,從源碼中可以看出它分別實現了List、RandomAccess、Cloneable以及Serializable,對于我們比較熟悉的應該是List和Serializable了。
核心字段
//可以看出這是重入鎖final transient ReentrantLock lock = new ReentrantLock();//底層數組,可以看出用了volatileprivate transient volatile Object[] array; 復制代碼通過以上兩個核心字段,可以猜出CopyOnWriteArrayList內部進行了加鎖來保證線程安全。那么它到底是如何實現的呢?我們繼續往下分析。
構造方法
/*** 默認構造方法,初始化了一個空數組*/public CopyOnWriteArrayList() {setArray(new Object[0]);} 復制代碼再來簡單看一下其他兩個構造方法,如下:
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);}public CopyOnWriteArrayList(E[] toCopyIn) {setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));}復制代碼修改
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;//設置array為newElementssetArray(newElements);} else {// Not quite a no-op; ensures volatile write semanticssetArray(elements);}return oldValue;} finally {//解鎖lock.unlock();}} 復制代碼可以看出在寫入數據時通過加鎖來保證線程安全,并且通過復制一個新數組。然后修改新數組中index對應的Value值,最后將新數組賦值給CopyOnWriteArrayList的底層數組array,也可以說是將array數組指向新數組,如下圖:
對于CopyOnWriteArrayList的set()方法,我們可以這樣理解。- 獲取舊數組
- 通過復制舊數組創建新數組
- 在新數組中進行修改
- array指向新數組
添加
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();}} 復制代碼可以看出與set()方法類似,也是通過加鎖的方式來保證線程安全。然后創建一個新數組來添加元素,最后將新數組賦值給array。
獲取
public E get(int index) {return get(getArray(), index);} @SuppressWarnings("unchecked") private E get(Object[] a, int index) {return (E) a[index];} 復制代碼從get()方法源碼中可以看出 ,在從CopyOnWriteArrayList獲取元素時并沒有進行加鎖。那么我們就要思考一個問題了,如果當寫入數據時,在沒有寫完數據的情況下。再去讀取數據,那么我們能準確的獲得數據嗎?而且重點是在get()方法中,是直接對底層數組array進行操作的。那就意味著在我們讀取數據時,有可能獲得的是舊數據。也就是說CopyOnWriteArrayList并不能保證數據的實時性,主要原因可以從以下幾點來思考。
- 在get()方法并沒有進行加鎖
- 讀取的是底層數組array的數據
刪除
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();}} 復制代碼可以看出remove()方法也是一樣的,通過加鎖來保證線程安全。
其他方法
/*** 獲取底層數組*/final Object[] getArray() {return array;}/*** 設置底層數組*/final void setArray(Object[] a) {array = a;}/*** 獲取數組大小*/public int size() {return getArray().length;}/*** 判斷數組是否為空*/ public boolean isEmpty() {return size() == 0;} 復制代碼優點與缺點
通過查看源碼,我們可以看出CopyOnWriteArrayList最大的優點就是線程安全,也是最突出的優點。它在讀取操作時沒有加鎖,只有當進行寫入操作時才會加鎖。這也體現出了CopyOnWriteArrayList的一個缺點,那就是無法讀取實時數據。因為可能當寫操作還沒有完成時,就進行了讀取操作,那么讀取的將是舊數據。
CopyOnWriteArraySet
接下來我們來簡單了解一下CopyOnWriteArrayList的表兄弟CopyOnWriteArraySet,主要源碼如下。
private final CopyOnWriteArrayList<E> al;public CopyOnWriteArraySet() {al = new CopyOnWriteArrayList<E>();} 復制代碼通過構造方法就可以看出初始化了一個CopyOnWriteArrayList,也就是說CopyOnWriteArraySet內部是用CopyOnWriteArrayList來實現的,我們再來看一下其他幾個方法。
public boolean add(E e) {return al.addIfAbsent(e);} public boolean remove(Object o) {return al.remove(o);} 復制代碼可以看出add()和remove()都是通過CopyOnWriteArrayList來實現。
總結
通過分析源碼,我們了解了CopyOnWriteArrayList的內部實現,以及它是如何保證線程安全的,還了解一下CopyOnWriteArraySet。
轉載于:https://juejin.im/post/5cbf2d0fe51d456e396318d2
總結
以上是生活随笔為你收集整理的Java基础之CopyOnWriteArrayList的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我读了这七本书,写了这篇关于如何高效阅读
- 下一篇: Sam Hartman 当选 Debia