Java集合源碼分析(三)Vector和Stack

前言

前面寫了一篇關(guān)于的是LinkedList的除了它的數(shù)據(jù)結(jié)構(gòu)稍微有一點(diǎn)復(fù)雜之外,其他的都很好理解的。這一篇講的可能大家在開發(fā)中很少去用到。但是有的時(shí)候也可能是會(huì)用到的!

注意在學(xué)習(xí)這一篇之前,需要有多線程的知識(shí):

1)鎖機(jī)制:對象鎖、方法鎖、類鎖

對象鎖就是方法鎖:就是在一個(gè)類中的方法上加上synchronized關(guān)鍵字,這就是給這個(gè)方法加鎖了。

類鎖:鎖的是整個(gè)類,當(dāng)有多個(gè)線程來聲明這個(gè)類的對象的時(shí)候?qū)?huì)被阻塞,直到擁有這個(gè)類鎖的對象被銷毀或者主動(dòng)釋放了類鎖。這個(gè)時(shí)候在被阻塞住的線程被挑選出一個(gè)占有該類鎖,

聲明該類的對象。其他線程繼續(xù)被阻塞住。例如:在類A上有關(guān)鍵字synchronized,那么就是給類A加了類鎖,線程1第一個(gè)聲明此類的實(shí)例,則線程1拿到了該類鎖,線程2在想聲明類A的對象,就會(huì)被阻塞。

2)在本文中,使用的是方法鎖。

3)每個(gè)對象只有一把鎖,有線程A,線程B,還有一個(gè)集合C類,線程A操作C拿到了集合中的鎖(在集合C中有用synchronized關(guān)鍵字修飾的),并且還沒有執(zhí)行完,那么線程A就不會(huì)釋放鎖,

當(dāng)輪到線程B去操作集合C中的方法時(shí) ,發(fā)現(xiàn)鎖被人拿走了,所以線程B只能等待那個(gè)拿到鎖的線程使用完,然后才能拿到鎖進(jìn)行相應(yīng)的操作。

一、Vector簡介

1.1、Vector概述

Vector

通過API中可以知道:

1)Vector是一個(gè)可變化長度的數(shù)組
    2)Vector增加長度通過的是capacity和capacityIncrement這兩個(gè)變量,目前還不知道如何實(shí)現(xiàn)自動(dòng)擴(kuò)增的,等會(huì)源碼分析
    3)Vector也可以獲得iterator和listIterator這兩個(gè)迭代器,并且他們發(fā)生的是fail-fast,而不是fail-safe,注意這里,不要覺得這個(gè)vector是線程安全就搞錯(cuò)了,具體分析在下面會(huì)說
    4)Vector是一個(gè)線程安全的類,如果使用需要線程安全就使用Vector,如果不需要,就使用arrayList
    5)Vector和ArrayList很類似,就少許的不一樣,從它繼承的類和實(shí)現(xiàn)的接口來看,跟arrayList一模一樣。

注意:現(xiàn)在的版本已經(jīng)是jdk1.7,還有更高的jdk1.8了,在開發(fā)中,建議不用vector,原因在文章的結(jié)束會(huì)有解釋,如果需要線程安全的集合類直接用java.util.concurrent包下的類。

二、Vector源碼分析

2.1、繼承結(jié)構(gòu)和層次關(guān)系

繼承結(jié)構(gòu)和層次關(guān)系
繼承結(jié)構(gòu)和層次關(guān)系

我們發(fā)現(xiàn)Vector的繼承關(guān)系和層次結(jié)構(gòu)和ArrayList中的一模一樣。

2.2、構(gòu)造方法

一共有四個(gè)構(gòu)造方法。最后兩個(gè)構(gòu)造方法是collection Framwork的規(guī)范要寫的構(gòu)造方法。

構(gòu)造方法作用:

1)初始化存儲(chǔ)元素的容器,也就是數(shù)組,elementData,

2)初始化capacityIncrement的大小,默認(rèn)是0,這個(gè)的作用就是擴(kuò)展數(shù)組的時(shí)候,增長的大小,為0則每次擴(kuò)展2倍

構(gòu)造方法

1)Vector():空構(gòu)造

/**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero. */
//看注釋,這個(gè)是一個(gè)空的Vector構(gòu)造方法,所以讓他使用內(nèi)置的數(shù)組,這里還不知道什么是內(nèi)置的數(shù)組,看它調(diào)用了自身另外一個(gè)帶一個(gè)參數(shù)的構(gòu)造器
    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構(gòu)造器用和帶有一個(gè)特定初始化容量用的,并且又調(diào)用了另外一個(gè)帶兩個(gè)參數(shù)的構(gòu)造器,并且給容量增長值(capacityIncrement=0)為0,查看vector中的變量可以發(fā)現(xiàn)capacityIncrement是一個(gè)成員變量
    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 */
//構(gòu)建一個(gè)有特定的初始化容量和容量增長值的空的Vector,
    public Vector(int initialCapacity, int capacityIncrement) {
        super();//調(diào)用父類的構(gòu)造,是個(gè)空構(gòu)造
        if (initialCapacity < 0)//小于0,會(huì)報(bào)非法參數(shù)異常
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity];//elementData是一個(gè)成員變量數(shù)組,初始化它,并給它初始化長度。默認(rèn)就是10,除非自己給值。
        this.capacityIncrement = capacityIncrement;//capacityIncrement的意思是如果要擴(kuò)增數(shù)組,每次增長該值,如果該值為0,那數(shù)組就變?yōu)閮杀兜脑L度,這個(gè)之后會(huì)分析到
    }

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變?yōu)閂ector,返回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是線程安全的,因?yàn)樵诜椒ㄇ懊婕恿藄ynchronized關(guān)鍵字,給該方法加鎖了,哪個(gè)線程先調(diào)用它,其它線程就得等著,如果不清楚的就去看看多線程的知識(shí),到后面我也會(huì)一一總結(jié)的。
    public synchronized boolean add(E e) {
        modCount++; //通過arrayList的源碼分析經(jīng)驗(yàn),這個(gè)方法應(yīng)該是在增加元素前,檢查容量是否夠用
        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) */
//這里注釋解釋,這個(gè)方法是異步(也就是能被多個(gè)線程同時(shí)訪問)的,原因是為了讓同步方法都能調(diào)用到這個(gè)檢測容量的方法,比如add的同時(shí),另一個(gè)線程調(diào)用了add的重載方法,那么兩個(gè)都需要同時(shí)查詢?nèi)萘繅虿粔颍赃@個(gè)就不需要用synchronized修飾了。因?yàn)椴粫?huì)發(fā)生線程不安全的問題
    private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code
        if (minCapacity - elementData.length > 0) //容量不夠,就擴(kuò)增,核心方法
         grow(minCapacity);
    }

grow(int)

//看一下這個(gè)方法,其實(shí)跟arrayList一樣,唯一的不同就是在擴(kuò)增數(shù)組的方式不一樣,如果capacityIncrement不為0,那么增長的長度就是capacityIncrement,如果為0,那么擴(kuò)增為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,這個(gè)就是在每個(gè)方法上比arrayList多了一個(gè)synchronized,其他都一樣。這里就不再分析了!

public synchronized E get(int index) { 
if (index >= elementCount) 
    throw new ArrayIndexOutOfBoundsException(index); 
    return elementData(index);
    }

三、Stack

Stack

現(xiàn)在來看看Vector的子類Stack,學(xué)過數(shù)據(jù)結(jié)構(gòu)都知道,這個(gè)就是棧的意思。那么該類就是跟棧的用法一樣了

通過查看他的方法,和查看api文檔,很容易就能知道他的特性。就幾個(gè)操作,出棧,入棧等,構(gòu)造方法也是空的,用的還是數(shù)組,父類中的構(gòu)造,跟父類一樣的擴(kuò)增方式,并且它的方法也是同步的,所以也是線程安全。

Stack

四、總結(jié)Vector和Stack

4.1、Vector總結(jié)(通過源碼分析)

1)Vector線程安全是因?yàn)樗姆椒ǘ技恿藄ynchronized關(guān)鍵字
2)Vector的本質(zhì)是一個(gè)數(shù)組,特點(diǎn)能是能夠自動(dòng)擴(kuò)增,擴(kuò)增的方式跟capacityIncrement的值有關(guān)
3)它也會(huì)fail-fast,還有一個(gè)fail-safe兩個(gè)的區(qū)別在下面的list總結(jié)中會(huì)講到

4.2、Stack的總結(jié)

1)對棧的一些操作,先進(jìn)后出
2)底層也是用數(shù)組實(shí)現(xiàn)的,因?yàn)槔^承了Vector
3)也是線程安全的

五、List總結(jié)

5.1、arrayList和LinkedList區(qū)別

arrayList底層是用數(shù)組實(shí)現(xiàn)的順序表,是隨機(jī)存取類型,可自動(dòng)擴(kuò)增,并且在初始化時(shí),數(shù)組的長度是0,只有在增加元素時(shí),長度才會(huì)增加。默認(rèn)是10,不能無限擴(kuò)增,有上限,在查詢操作的時(shí)候性能更好

LinkedList底層是用鏈表來實(shí)現(xiàn)的,是一個(gè)雙向鏈表,注意這里不是雙向循環(huán)鏈表,順序存取類型。在源碼中,似乎沒有元素個(gè)數(shù)的限制。應(yīng)該能無限增加下去,直到內(nèi)存滿了在進(jìn)行刪除,增加操作時(shí)性能更好。

兩個(gè)都是線程不安全的,在iterator時(shí),會(huì)發(fā)生fail-fast。

5.2、arrayList和Vector的區(qū)別

arrayList線程不安全,在用iterator,會(huì)發(fā)生fail-fast

Vector線程安全,因?yàn)樵诜椒ㄇ凹恿薙ynchronized關(guān)鍵字。也會(huì)發(fā)生fail-fast

5.3、fail-fast和fail-safe區(qū)別和什么情況下會(huì)發(fā)生

簡單的來說:在java.util下的集合都是發(fā)生fail-fast,而在java.util.concurrent下的發(fā)生的都是fail-safe。

1)fail-fast

快速失敗,例如在arrayList中使用迭代器遍歷時(shí),有另外的線程對arrayList的存儲(chǔ)數(shù)組進(jìn)行了改變,比如add、delete、等使之發(fā)生了結(jié)構(gòu)上的改變,

所以Iterator就會(huì)快速報(bào)一個(gè)java.util.ConcurrentModificationException 異常(并發(fā)修改異常),這就是快速失敗。

2)fail-safe

安全失敗,在java.util.concurrent下的類,都是線程安全的類,他們在迭代的過程中,如果有線程進(jìn)行結(jié)構(gòu)的改變,不會(huì)報(bào)異常,而是正常遍歷,這就是安全失敗。

3)為什么在java.util.concurrent包下對集合有結(jié)構(gòu)的改變,卻不會(huì)報(bào)異常?

在concurrent下的集合類增加元素的時(shí)候使用Arrays.copyOf()來拷貝副本,在副本上增加元素,如果有其他線程在此改變了集合的結(jié)構(gòu),那也是在副本上的改變,而不是影響到原集合,

迭代器還是照常遍歷,遍歷完之后,改變原引用指向副本,所以總的一句話就是如果在次包下的類進(jìn)行增加刪除,就會(huì)出現(xiàn)一個(gè)副本。所以能防止fail-fast,這種機(jī)制并不會(huì)出錯(cuò),所以我們叫這種現(xiàn)象為fail-safe。

4)vector也是線程安全的,為什么是fail-fast呢?

這里搞清楚一個(gè)問題,并不是說線程安全的集合就不會(huì)報(bào)fail-fast,而是報(bào)fail-safe,你得搞清楚前面所說答案的原理,出現(xiàn)fail-safe是因?yàn)樗麄冊趯?shí)現(xiàn)增刪的底層機(jī)制不一樣,就像上面說的,

會(huì)有一個(gè)副本,而像arrayList、linekdList、verctor等,他們底層就是對著真正的引用進(jìn)行操作,所以才會(huì)發(fā)生異常。

5)既然是線程安全的,為什么在迭代的時(shí)候,還會(huì)有別的線程來改變其集合的結(jié)構(gòu)呢(也就是對其刪除和增加等操作)?

首先,我們迭代的時(shí)候,根本就沒用到集合中的刪除、增加,查詢的操作,就拿vector來說,我們都沒有用那些加鎖的方法,

也就是方法鎖放在那沒人拿,在迭代的過程中,有人拿了那把鎖,我們也沒有辦法,因?yàn)槟前焰i就放在那邊。

總結(jié)劃重點(diǎn):

快速失敗(fail—fast)

在用迭代器遍歷一個(gè)集合對象時(shí),如果遍歷過程中對集合對象的內(nèi)容進(jìn)行了修改(增加、刪除、修改),則會(huì)拋出Concurrent Modification Exception。

原理:迭代器在遍歷時(shí)直接訪問集合中的內(nèi)容,并且在遍歷過程中使用一個(gè) modCount 變量。集合在被遍歷期間如果內(nèi)容發(fā)生變化,就會(huì)改變modCount的值。每當(dāng)?shù)魇褂胔ashNext()/next()遍歷下一個(gè)元素之前,都會(huì)檢測modCount變量是否為expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。

注意:這里異常的拋出條件是檢測到 modCount!=expectedmodCount 這個(gè)條件。如果集合發(fā)生變化時(shí)修改modCount值剛好又設(shè)置為了expectedmodCount值,則異常不會(huì)拋出。因此,不能依賴于這個(gè)異常是否拋出而進(jìn)行并發(fā)操作的編程,這個(gè)異常只建議用于檢測并發(fā)修改的bug。

場景:java.util包下的集合類都是快速失敗的,不能在多線程下發(fā)生并發(fā)修改(迭代過程中被修改)。



安全失?。╢ail—safe)

采用安全失敗機(jī)制的集合容器,在遍歷時(shí)不是直接在集合內(nèi)容上訪問的,而是先復(fù)制原有集合內(nèi)容,在拷貝的集合上進(jìn)行遍歷。

原理:由于迭代時(shí)是對原集合的拷貝進(jìn)行遍歷,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會(huì)觸發(fā)Concurrent Modification Exception。

缺點(diǎn):基于拷貝內(nèi)容的優(yōu)點(diǎn)是避免了Concurrent Modification Exception,但同樣地,迭代器并不能訪問到修改后的內(nèi)容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發(fā)生的修改迭代器是不知道的。

場景:java.util.concurrent包下的容器都是安全失敗,可以在多線程下并發(fā)使用,并發(fā)修改。

5.4、舉例說明fail-fast和fail-safe的區(qū)別

1)fail-fast·

fail-fast

2)fail-safe

通過CopyOnWriteArrayList這個(gè)類來做實(shí)驗(yàn),不用管這個(gè)類的作用,但是他確實(shí)沒有報(bào)異常,并且還通過第二次打印,來驗(yàn)證了上面我們說創(chuàng)建了副本的事情。

原理是在添加操作時(shí)會(huì)創(chuàng)建副本,在副本上進(jìn)行添加操作,等迭代器遍歷結(jié)束后,會(huì)將原引用改為副本引用,所以我們在創(chuàng)建了一個(gè)list的迭代器,結(jié)果打印的就是123444了,

證明了確實(shí)改變成為了副本引用,后面為什么是三個(gè)4,原因是我們循環(huán)了3次,不久添加了3個(gè)4嗎。如果還感覺不爽的話,看下add的源碼。

fast-safe
fast-safe

5.5、為什么現(xiàn)在都不提倡使用vector了

1)vector實(shí)現(xiàn)線程安全的方法是在每個(gè)操作方法上加鎖,這些鎖并不是必須要的,在實(shí)際開發(fā)中,一般都市通過鎖一系列的操作來實(shí)現(xiàn)線程安全,也就是說將需要同步的資源放一起加鎖來保證線程安全,

2)如果多個(gè)Thread并發(fā)執(zhí)行一個(gè)已經(jīng)加鎖的方法,但是在該方法中,又有vector的存在,vector本身實(shí)現(xiàn)中已經(jīng)加鎖了,那么相當(dāng)于鎖上又加鎖,會(huì)造成額外的開銷,

3)就如上面第三個(gè)問題所說的,vector還有fail-fast的問題,也就是說它也無法保證遍歷安全,在遍歷時(shí)又得額外加鎖,又是額外的開銷,還不如直接用arrayList,然后再加鎖呢。

總結(jié):Vector在你不需要進(jìn)行線程安全的時(shí)候,也會(huì)給你加鎖,也就導(dǎo)致了額外開銷,所以在jdk1.5之后就被棄用了,現(xiàn)在如果要用到線程安全的集合,都是從java.util.concurrent包下去拿相應(yīng)的類。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.Java集合框架是什么?說出一些集合框架的優(yōu)點(diǎn)? 每種編程語言中都有集合,最初的Java版本包含幾種集合類:V...
    獨(dú)念白閱讀 885評論 0 2
  • 1.Java集合框架是什么?說出一些集合框架的優(yōu)點(diǎn)? 每種編程語言中都有集合,最初的Java版本包含幾種集合類:V...
    hutuxiaogui閱讀 730評論 0 10
  • Java集合是java提供的工具包,包含了常用的數(shù)據(jù)結(jié)構(gòu):集合、鏈表、隊(duì)列、棧、數(shù)組、映射等。Java集合工具包位...
    聶叼叼閱讀 551評論 0 2
  • 什么樣的日子都過過了,青春奮斗的學(xué)習(xí)生涯,職場上的爾虞我詐,辭職后悠閑被人養(yǎng)的日子,精分后的恍然隔世,慢條斯理浪費(fèi)...
    Amanda老少女閱讀 282評論 0 0
  • 一出門就遇上大霧,本該明朗的天,此時(shí)霧蒙蒙的,能見度極小,不時(shí)的有霧水滴落下,一會(huì)兒就把你的頭發(fā)打濕了,想回去拿了...
    依依_f0cc閱讀 682評論 1 2

友情鏈接更多精彩內(nèi)容