您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么使用CopyOnWriteArrayList”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么使用CopyOnWriteArrayList”吧!
由于本文的重點不是Vector
集合,因此只是簡單的分析一下Vector
的初始化方法和添加元素的方法。
Vector
的底層實現和ArrayList
一樣,都是由數組實現的。
Vector
的主要變量如下:
/** * 存放元素的數組 */ protected Object[] elementData; /** * 元素個數 */ protected int elementCount; /** * 擴容自增容量大小 */ protected int capacityIncrement;
Vector
的初始化提供了三個方法,除了可以指定初始容量的大小,還可以指定擴容容量的大小。構造器分別如下:
無參構造器
public Vector() { this(10); }
指定初始化容量的構造器
public Vector(int initialCapacity) { this(initialCapacity, 0); }
指定初始化容量和擴容容量大小的構造器
public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
從上面的構造器中可以看出,如果調用無參構造器,則會創建一個初始化容量為10
,擴容容量為0
的Vector
集合。
Vector
的擴容機制和ArrayList
的很像,如果不清楚ArrayList
的擴容機制,可以看看這篇文章。這里我們直接看Vector
的擴容方法grow
。
private void grow(int minCapacity) { // overflow-conscious code // 初始化數組的長度,默認為10 int oldCapacity = elementData.length; // 是否指定擴容容量,不指定擴容為原來的2倍 int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
通過上面的方法,我們可以看出,如果指定了擴容容量的大小則擴容的新數組大小為原來的數組加上擴容容量的大小,如果不指定擴容容量的大小則擴容的新數組大小為原來數組大小的2
倍。這樣擴容為原來的2倍是很消耗空間的,這也是Vector
被棄用的原因之一。
除此之外,看過源碼的同學可能發現了,Vector
集合的所有操作元素的方法都加了synchronized
關鍵字,這就導致了操作Vector
的效率會非常低,在開發中,往往讀操作的使用頻率會遠高于其他操作,而CopyOnWriteArrayList
就是這樣一種讀操作效率遠高于寫操作效率的List,一起來看看。
CopyOnWriteArrayList
類圖:
CopyOnWrite
簡稱COW,根據名字來看就是寫入時復制。意思就是大家共同去訪問一個資源,如果有人想要去修改這個資源的時候,就需要復制一個副本,去修改這個副本,而對于其他人來說訪問得資源還是原來的,不會發生變化。
CopyOnWriteArrayList
底層是也是有數組實現的。 本文我們只解讀添加元素和讀取元素的區別,刪除修改元素原理和添加元素差不多,操作時都需要進行加鎖,而讀操作不會加鎖。
CopyOnWriteArrayList
主要有以下兩個變量:
// 獨占鎖 final transient ReentrantLock lock = new ReentrantLock(); // 存放元素的數組 private transient volatile Object[] array;
我們仔細來分析一下上面兩個屬性,這兩個思想是 CopyOnWriteArrayList
的核心 。
lock:ReentrantLock,獨占鎖,多線程運行的情況下,只有一個線程會獲得這個鎖,只有釋放鎖后其他線程才能獲得。
array:存放數據的數組,關鍵是被volatile
修飾了,被volatile
修飾,就保證了可見性,也就是一個線程修改后,其他線程立即可見。
最常用的初始化方式如下:
/** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); } /** * Sets the array. */ final void setArray(Object[] a) { array = a; }
初始化只是創建了一個空的數組,并將array
指向它。
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { // 獲取原來的數組 Object[] elements = getArray(); // 原來數組的長度 int len = elements.length; // 創建一個長度+1的新數組,并將原來數組的元素復制給新數組 Object[] newElements = Arrays.copyOf(elements, len + 1); // 元素放在新數組末尾 newElements[len] = e; // array指向新數組 setArray(newElements); return true; } finally { lock.unlock(); } }
添加數組的步驟如下:
獲得獨占鎖,將添加功能加鎖
獲取原來的數組,并得到其長度
創建一個長度為原來數組長度+1的數組,并拷貝原來的元素給新數組
追加元素到新數組末尾
指向新數組
釋放鎖
這個過程是線程安全的,COW的核心思想就是每次修改的時候拷貝一個新的資源去修改,add()
方法再拷貝新資源的時候將數組容量+1,這樣雖然每次添加元素都會浪費一定的空間,但是數組的長度正好是元素的長度,也在一定程度上節省了擴容的開銷。
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; }
由于CopyOnWriteArrayList
的底層數組長度,本身就是元素大小,因此size()
方法只要返回數組長度就可以了。
到此,相信大家對“怎么使用CopyOnWriteArrayList”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。