前言
前面寫了一篇關于的是LinkedList的除了它的數據結構稍微有一點復雜之外,其他的都很好理解的。這一篇講的可能大家在開發中很少去用到。但是有的時候也可能是會用到的!
注意在學習這一篇之前,需要有多線程的知識:
1)鎖機制:對象鎖、方法鎖、類鎖
對象鎖就是方法鎖:就是在一個類中的方法上加上synchronized關鍵字,這就是給這個方法加鎖了。
類鎖:鎖的是整個類,當有多個線程來聲明這個類的對象的時候將會被阻塞,直到擁有這個類鎖的對象被銷毀或者主動釋放了類鎖。這個時候在被阻塞住的線程被挑選出一個占有該類鎖,
聲明該類的對象。其他線程繼續被阻塞住。例如:在類A上有關鍵字synchronized,那么就是給類A加了類鎖,線程1第一個聲明此類的實例,則線程1拿到了該類鎖,線程2在想聲明類A的對象,就會被阻塞。
2)在本文中,使用的是方法鎖。
3)每個對象只有一把鎖,有線程A,線程B,還有一個集合C類,線程A操作C拿到了集合中的鎖(在集合C中有用synchronized關鍵字修飾的),并且還沒有執行完,那么線程A就不會釋放鎖,
當輪到線程B去操作集合C中的方法時 ,發現鎖被人拿走了,所以線程B只能等待那個拿到鎖的線程使用完,然后才能拿到鎖進行相應的操作。
一、Vector簡介
1.1、Vector概述
通過API中可以知道:
1)Vector是一個可變化長度的數組
2)Vector增加長度通過的是capacity和capacityIncrement這兩個變量,目前還不知道如何實現自動擴增的,等會源碼分析
3)Vector也可以獲得iterator和listIterator這兩個迭代器,并且他們發生的是fail-fast,而不是fail-safe,注意這里,不要覺得這個vector是線程安全就搞錯了,具體分析在下面會說
4)Vector是一個線程安全的類,如果使用需要線程安全就使用Vector,如果不需要,就使用arrayList
5)Vector和ArrayList很類似,就少許的不一樣,從它繼承的類和實現的接口來看,跟arrayList一模一樣。
注意:現在的版本已經是jdk1.7,還有更高的jdk1.8了,在開發中,建議不用vector,原因在文章的結束會有解釋,如果需要線程安全的集合類直接用java.util.concurrent包下的類。
二、Vector源碼分析
2.1、繼承結構和層次關系
我們發現Vector的繼承關系和層次結構和ArrayList中的一模一樣。
2.2、構造方法
一共有四個構造方法。最后兩個構造方法是collection Framwork的規范要寫的構造方法。
構造方法作用:
1)初始化存儲元素的容器,也就是數組,elementData,
2)初始化capacityIncrement的大小,默認是0,這個的作用就是擴展數組的時候,增長的大小,為0則每次擴展2倍
1)Vector():空構造
/**
* Constructs an empty vector so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero. */
//看注釋,這個是一個空的Vector構造方法,所以讓他使用內置的數組,這里還不知道什么是內置的數組,看它調用了自身另外一個帶一個參數的構造器
public Vector() { this(10);
}
2)Vector(int)
/**
* Constructs an empty vector with the specified initial capacity and
* with its capacity increment equal to zero.
*
* @param initialCapacity the initial capacity of the vector
* @throws IllegalArgumentException if the specified initial capacity
* is negative */
//注釋說,給空的cector構造器用和帶有一個特定初始化容量用的,并且又調用了另外一個帶兩個參數的構造器,并且給容量增長值(capacityIncrement=0)為0,查看vector中的變量可以發現capacityIncrement是一個成員變量
public Vector(int initialCapacity) { this(initialCapacity, 0);
}
3)Vector(int,int)
/**
* Constructs an empty vector with the specified initial capacity and
* capacity increment.
*
* @param initialCapacity the initial capacity of the vector
* @param capacityIncrement the amount by which the capacity is
* increased when the vector overflows
* @throws IllegalArgumentException if the specified initial capacity
* is negative */
//構建一個有特定的初始化容量和容量增長值的空的Vector,
public Vector(int initialCapacity, int capacityIncrement) {
super();//調用父類的構造,是個空構造
if (initialCapacity < 0)//小于0,會報非法參數異常
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity];//elementData是一個成員變量數組,初始化它,并給它初始化長度。默認就是10,除非自己給值。
this.capacityIncrement = capacityIncrement;//capacityIncrement的意思是如果要擴增數組,每次增長該值,如果該值為0,那數組就變為兩倍的原長度,這個之后會分析到
}
4)Vector(Collection<? extends E> c)
/**
* Constructs a vector containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this
* vector
* @throws NullPointerException if the specified collection is null
* @since 1.2 */
//將集合c變為Vector,返回Vector的迭代器。
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
2.3、核心方法
2.3.1、add()方法
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2 */
//就是在vector中的末尾追加元素。但是看方法,synchronized,明白了為什么vector是線程安全的,因為在方法前面加了synchronized關鍵字,給該方法加鎖了,哪個線程先調用它,其它線程就得等著,如果不清楚的就去看看多線程的知識,到后面我也會一一總結的。
public synchronized boolean add(E e) {
modCount++; //通過arrayList的源碼分析經驗,這個方法應該是在增加元素前,檢查容量是否夠用
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e; return true;
}
ensureCapacityHelper(int)
/**
* This implements the unsynchronized semantics of ensureCapacity.
* Synchronized methods in this class can internally call this
* method for ensuring capacity without incurring the cost of an
* extra synchronization.
*
* @see #ensureCapacity(int) */
//這里注釋解釋,這個方法是異步(也就是能被多個線程同時訪問)的,原因是為了讓同步方法都能調用到這個檢測容量的方法,比如add的同時,另一個線程調用了add的重載方法,那么兩個都需要同時查詢容量夠不夠,所以這個就不需要用synchronized修飾了。因為不會發生線程不安全的問題
private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code
if (minCapacity - elementData.length > 0) //容量不夠,就擴增,核心方法
grow(minCapacity);
}
grow(int)
//看一下這個方法,其實跟arrayList一樣,唯一的不同就是在擴增數組的方式不一樣,如果capacityIncrement不為0,那么增長的長度就是capacityIncrement,如果為0,那么擴增為2倍的原容量
private void grow(int minCapacity) { // overflow-conscious code
int oldCapacity = elementData.length; 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);
}
感覺只要你能看的懂ArrayList,這個就是在每個方法上比arrayList多了一個synchronized,其他都一樣。這里就不再分析了!
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
三、Stack
現在來看看Vector的子類Stack,學過數據結構都知道,這個就是棧的意思。那么該類就是跟棧的用法一樣了
通過查看他的方法,和查看api文檔,很容易就能知道他的特性。就幾個操作,出棧,入棧等,構造方法也是空的,用的還是數組,父類中的構造,跟父類一樣的擴增方式,并且它的方法也是同步的,所以也是線程安全。
四、總結Vector和Stack
4.1、Vector總結(通過源碼分析)
1)Vector線程安全是因為它的方法都加了synchronized關鍵字
2)Vector的本質是一個數組,特點能是能夠自動擴增,擴增的方式跟capacityIncrement的值有關
3)它也會fail-fast,還有一個fail-safe兩個的區別在下面的list總結中會講到
4.2、Stack的總結
1)對棧的一些操作,先進后出
2)底層也是用數組實現的,因為繼承了Vector
3)也是線程安全的
五、List總結
5.1、arrayList和LinkedList區別
arrayList底層是用數組實現的順序表,是隨機存取類型,可自動擴增,并且在初始化時,數組的長度是0,只有在增加元素時,長度才會增加。默認是10,不能無限擴增,有上限,在查詢操作的時候性能更好
LinkedList底層是用鏈表來實現的,是一個雙向鏈表,注意這里不是雙向循環鏈表,順序存取類型。在源碼中,似乎沒有元素個數的限制。應該能無限增加下去,直到內存滿了在進行刪除,增加操作時性能更好。
兩個都是線程不安全的,在iterator時,會發生fail-fast。
5.2、arrayList和Vector的區別
arrayList線程不安全,在用iterator,會發生fail-fast
Vector線程安全,因為在方法前加了Synchronized關鍵字。也會發生fail-fast
5.3、fail-fast和fail-safe區別和什么情況下會發生
簡單的來說:在java.util下的集合都是發生fail-fast,而在java.util.concurrent下的發生的都是fail-safe。
1)fail-fast
快速失敗,例如在arrayList中使用迭代器遍歷時,有另外的線程對arrayList的存儲數組進行了改變,比如add、delete、等使之發生了結構上的改變,
所以Iterator就會快速報一個java.util.ConcurrentModificationException 異常(并發修改異常),這就是快速失敗。
2)fail-safe
安全失敗,在java.util.concurrent下的類,都是線程安全的類,他們在迭代的過程中,如果有線程進行結構的改變,不會報異常,而是正常遍歷,這就是安全失敗。
3)為什么在java.util.concurrent包下對集合有結構的改變,卻不會報異常?
在concurrent下的集合類增加元素的時候使用Arrays.copyOf()來拷貝副本,在副本上增加元素,如果有其他線程在此改變了集合的結構,那也是在副本上的改變,而不是影響到原集合,
迭代器還是照常遍歷,遍歷完之后,改變原引用指向副本,所以總的一句話就是如果在次包下的類進行增加刪除,就會出現一個副本。所以能防止fail-fast,這種機制并不會出錯,所以我們叫這種現象為fail-safe。
4)vector也是線程安全的,為什么是fail-fast呢?
這里搞清楚一個問題,并不是說線程安全的集合就不會報fail-fast,而是報fail-safe,你得搞清楚前面所說答案的原理,出現fail-safe是因為他們在實現增刪的底層機制不一樣,就像上面說的,
會有一個副本,而像arrayList、linekdList、verctor等,他們底層就是對著真正的引用進行操作,所以才會發生異常。
5)既然是線程安全的,為什么在迭代的時候,還會有別的線程來改變其集合的結構呢(也就是對其刪除和增加等操作)?
首先,我們迭代的時候,根本就沒用到集合中的刪除、增加,查詢的操作,就拿vector來說,我們都沒有用那些加鎖的方法,
也就是方法鎖放在那沒人拿,在迭代的過程中,有人拿了那把鎖,我們也沒有辦法,因為那把鎖就放在那邊。
總結劃重點:
快速失敗(fail—fast)
在用迭代器遍歷一個集合對象時,如果遍歷過程中對集合對象的內容進行了修改(增加、刪除、修改),則會拋出Concurrent Modification Exception。
原理:迭代器在遍歷時直接訪問集合中的內容,并且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變量是否為expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。
注意:這里異常的拋出條件是檢測到 modCount!=expectedmodCount 這個條件。如果集合發生變化時修改modCount值剛好又設置為了expectedmodCount值,則異常不會拋出。因此,不能依賴于這個異常是否拋出而進行并發操作的編程,這個異常只建議用于檢測并發修改的bug。
場景:java.util包下的集合類都是快速失敗的,不能在多線程下發生并發修改(迭代過程中被修改)。
安全失?。╢ail—safe)
采用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先復制原有集合內容,在拷貝的集合上進行遍歷。
原理:由于迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會觸發Concurrent Modification Exception。
缺點:基于拷貝內容的優點是避免了Concurrent Modification Exception,但同樣地,迭代器并不能訪問到修改后的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。
場景:java.util.concurrent包下的容器都是安全失敗,可以在多線程下并發使用,并發修改。
5.4、舉例說明fail-fast和fail-safe的區別
1)fail-fast·
2)fail-safe
通過CopyOnWriteArrayList這個類來做實驗,不用管這個類的作用,但是他確實沒有報異常,并且還通過第二次打印,來驗證了上面我們說創建了副本的事情。
原理是在添加操作時會創建副本,在副本上進行添加操作,等迭代器遍歷結束后,會將原引用改為副本引用,所以我們在創建了一個list的迭代器,結果打印的就是123444了,
證明了確實改變成為了副本引用,后面為什么是三個4,原因是我們循環了3次,不久添加了3個4嗎。如果還感覺不爽的話,看下add的源碼。
5.5、為什么現在都不提倡使用vector了
1)vector實現線程安全的方法是在每個操作方法上加鎖,這些鎖并不是必須要的,在實際開發中,一般都市通過鎖一系列的操作來實現線程安全,也就是說將需要同步的資源放一起加鎖來保證線程安全,
2)如果多個Thread并發執行一個已經加鎖的方法,但是在該方法中,又有vector的存在,vector本身實現中已經加鎖了,那么相當于鎖上又加鎖,會造成額外的開銷,
3)就如上面第三個問題所說的,vector還有fail-fast的問題,也就是說它也無法保證遍歷安全,在遍歷時又得額外加鎖,又是額外的開銷,還不如直接用arrayList,然后再加鎖呢。
總結:Vector在你不需要進行線程安全的時候,也會給你加鎖,也就導致了額外開銷,所以在jdk1.5之后就被棄用了,現在如果要用到線程安全的集合,都是從java.util.concurrent包下去拿相應的類。