注:本系列文章中用到的jdk版本均爲
java8
相比不少同窗在剛接觸Java集合的時候,線程安全的List用的必定是Vector
。可是如今用到的線程安全的List通常都會用CopyOnWriteArrayList
,不多有人再去用Vector
了,至於爲何,文章中會具體說到。接下來,咱們先來簡單分析如下Vector
的源碼。java
因爲本文的重點不是Vector
集合,所以只是簡單的分析一下Vector
的初始化方法和添加元素的方法。面試
Vector
的底層實現和ArrayList
同樣,都是由數組實現的。數組
Vector
的主要變量以下:安全
/** * 存放元素的數組 */ protected Object[] elementData; /** * 元素個數 */ protected int elementCount; /** * 擴容自增容量大小 */ protected int capacityIncrement;
Vector
的初始化提供了三個方法,除了能夠指定初始容量的大小,還能夠指定擴容容量的大小。構造器分別以下:微信
無參構造器多線程
public Vector() { this(10); }
指定初始化容量的構造器dom
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
集合。this
Vector
的擴容機制和ArrayList
的很像,若是不清楚ArrayList
的擴容機制,能夠看看這篇文章。這裏咱們直接看Vector
的擴容方法grow
。spa
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
的核心 。
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(); } }
添加數組的步驟以下:
這個過程是線程安全的,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()
方法只要返回數組長度就能夠了。
Vector
和CopyOnWriteArrayList
都是線程安全的List,底層都是數組實現的,Vector
的每一個方法都進行了加鎖,而CopyOnWriteArrayList
的讀操做是不加鎖的,所以CopyOnWriteArrayList
的讀性能遠高於Vector
,Vector
每次擴容的大小都是原來數組大小的2
倍,而CopyOnWriteArrayList
不須要擴容,經過COW思想就能使數組容量知足要求。兩個集合都是先了RandomAccess
接口,支持隨機讀取,所以更加推薦使用for循環進行遍歷。在開發中,讀操做會遠遠多於其餘操做,所以使用CopyOnWriteArrayList
集合效率更高。
若是以爲文章不錯,歡迎關注、點贊、收藏,大家的支持是我創做的動力,感謝你們。
若是文章寫的有問題,請不要吝惜文筆,歡迎留言指出,我會及時覈查修改。
若是你還想看到更多別的東西,能夠微信搜索「Java旅途」進行關注。「Java旅途」目前已經整理各類中間件的使用教程及各種Java相關的面試題。掃描下方二維碼進行關注就能夠獲得這些資料。