為啥線程安全的List推薦使用CopyOnWriteArrayList瘦锹,不是Vector籍嘹?

為什么線程安全的List推薦使用CopyOnWriteArrayList,而不是Vector沼本?

注:本系列文章中用到的jdk版本均為java8

相比很多同學(xué)在剛接觸Java集合的時(shí)候噩峦,線程安全的List用的一定是Vector。但是現(xiàn)在用到的線程安全的List一般都會(huì)用CopyOnWriteArrayList抽兆,很少有人再去用Vector了识补,至于為什么,文章中會(huì)具體說(shuō)到辫红。接下來(lái)凭涂,我們先來(lái)簡(jiǎn)單分析一下Vector的源碼。

一贴妻、Vector集合源碼簡(jiǎn)析

由于本文的重點(diǎn)不是Vector集合切油,因此只是簡(jiǎn)單地分析一下Vector的初始化方法和添加元素的方法。

Vector的底層實(shí)現(xiàn)和ArrayList一樣名惩,都是由數(shù)組實(shí)現(xiàn)的澎胡。

Vector的主要變量如下:

/**
 * 存放元素的數(shù)組
 */
protected Object[] elementData;
/**
 * 元素個(gè)數(shù)
 */
protected int elementCount;
/**
 * 擴(kuò)容自增容量大小
 */
protected int capacityIncrement;

1.1 Vector初始化

Vector的初始化提供了三個(gè)方法,除了可以指定初始容量的大小娩鹉,還可以指定擴(kuò)容容量的大小攻谁。構(gòu)造器分別如下:

無(wú)參構(gòu)造器

public Vector() {
    this(10);
}

指定初始化容量的構(gòu)造器

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

指定初始化容量和擴(kuò)容容量大小的構(gòu)造器

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

從上面的構(gòu)造器中可以看出,如果調(diào)用無(wú)參構(gòu)造器弯予,則會(huì)創(chuàng)建一個(gè)初始化容量為10戚宦,擴(kuò)容容量為0的Vector集合。

1.2 如何擴(kuò)容

Vector的擴(kuò)容機(jī)制和ArrayList的很像锈嫩,如果不清楚ArrayList的擴(kuò)容機(jī)制受楼,可以看看這篇文章垦搬。這里我們直接看Vector的擴(kuò)容方法grow。

private void grow(int minCapacity) {
    // overflow-conscious code
    // 初始化數(shù)組的長(zhǎng)度艳汽,默認(rèn)為10
    int oldCapacity = elementData.length;
    // 是否指定擴(kuò)容容量猴贰,不指定擴(kuò)容為原來(lái)的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);
}

通過(guò)上面的方法,我們可以看出骚灸,如果指定了擴(kuò)容容量的大小則擴(kuò)容的新數(shù)組大小為原來(lái)的數(shù)組加上擴(kuò)容容量的大小糟趾,如果不指定擴(kuò)容容量的大小則擴(kuò)容的新數(shù)組大小為原來(lái)數(shù)組大小的2倍。這樣擴(kuò)容為原來(lái)的2倍是很消耗空間的甚牲,這也是Vector被棄用的原因之一义郑。

除此之外,看過(guò)源碼的同學(xué)可能發(fā)現(xiàn)了丈钙,Vector集合的所有操作元素的方法都加了synchronized關(guān)鍵字非驮,這就導(dǎo)致了操作Vector的效率會(huì)非常低,在開(kāi)發(fā)中雏赦,往往讀操作的使用頻率會(huì)遠(yuǎn)高于其他操作劫笙,而CopyOnWriteArrayList就是這樣一種讀操作效率遠(yuǎn)高于寫(xiě)操作效率的List,一起來(lái)看看星岗。

二填大、CopyOnWriteArrayList源碼簡(jiǎn)析

CopyOnWriteArrayList類(lèi)圖:

image

2.1 CopyOnWrite思想

CopyOnWrite簡(jiǎn)稱(chēng)COW,根據(jù)名字來(lái)看就是寫(xiě)入時(shí)復(fù)制俏橘。意思就是大家共同去訪問(wèn)一個(gè)資源允华,如果有人想要去修改這個(gè)資源的時(shí)候,就需要復(fù)制一個(gè)副本寥掐,去修改這個(gè)副本靴寂,而對(duì)于其他人來(lái)說(shuō)訪問(wèn)得資源還是原來(lái)的,不會(huì)發(fā)生變化召耘。

2.2 初始化CopyOnWriteArrayList

CopyOnWriteArrayList 底層是也是有數(shù)組實(shí)現(xiàn)的百炬。 本文我們只解讀添加元素和讀取元素的區(qū)別,刪除修改元素原理和添加元素差不多污它,操作時(shí)都需要進(jìn)行加鎖剖踊,而讀操作不會(huì)加鎖。

CopyOnWriteArrayList主要有以下兩個(gè)變量:

// 獨(dú)占鎖
final transient ReentrantLock lock = new ReentrantLock();

// 存放元素的數(shù)組
private transient volatile Object[] array;

我們仔細(xì)來(lái)分析一下上面兩個(gè)屬性衫贬,這兩個(gè)思想是 CopyOnWriteArrayList 的核心 蜜宪。

  • lock:ReentrantLock,獨(dú)占鎖祥山,多線程運(yùn)行的情況下,只有一個(gè)線程會(huì)獲得這個(gè)鎖掉伏,只有釋放鎖后其他線程才能獲得缝呕。
  • array:存放數(shù)據(jù)的數(shù)組澳窑,關(guān)鍵是被volatile修飾了,被volatile修飾供常,就保證了可見(jiàn)性摊聋,也就是一個(gè)線程修改后,其他線程立即可見(jiàn)栈暇。

最常用的初始化方式如下:

/**
  * Creates an empty list.
  */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

/**
  * Sets the array.
  */
final void setArray(Object[] a) {
    array = a;
}

初始化只是創(chuàng)建了一個(gè)空的數(shù)組麻裁,并將array指向它。

2.3 添加元素

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 獲取原來(lái)的數(shù)組
        Object[] elements = getArray();
        // 原來(lái)數(shù)組的長(zhǎng)度
        int len = elements.length;
        // 創(chuàng)建一個(gè)長(zhǎng)度+1的新數(shù)組源祈,并將原來(lái)數(shù)組的元素復(fù)制給新數(shù)組
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 元素放在新數(shù)組末尾
        newElements[len] = e;
        // array指向新數(shù)組
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

添加數(shù)組的步驟如下:

  1. 獲得獨(dú)占鎖煎源,將添加功能加鎖
  2. 獲取原來(lái)的數(shù)組,并得到其長(zhǎng)度
  3. 創(chuàng)建一個(gè)長(zhǎng)度為原來(lái)數(shù)組長(zhǎng)度+1的數(shù)組香缺,并拷貝原來(lái)的元素給新數(shù)組
  4. 追加元素到新數(shù)組末尾
  5. 指向新數(shù)組
  6. 釋放鎖

這個(gè)過(guò)程是線程安全的手销,COW的核心思想就是每次修改的時(shí)候拷貝一個(gè)新的資源去修改,add()方法在拷貝新資源的時(shí)候?qū)?shù)組容量+1图张,這樣雖然每次添加元素都會(huì)浪費(fèi)一定的空間锋拖,但是數(shù)組的長(zhǎng)度正好是元素的長(zhǎng)度,也在一定程度上節(jié)省了擴(kuò)容的開(kāi)銷(xiāo)祸轮。

2.4 獲取元素

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];
}

讀操作是天然安全的操作兽埃,而且數(shù)組本身會(huì)進(jìn)行檢查越界問(wèn)題,因此獲取元素的方法很簡(jiǎn)單适袜,只是根據(jù)索引獲取該元素柄错。

public int size() {
    return getArray().length;
}

由于CopyOnWriteArrayList的底層數(shù)組長(zhǎng)度,本身就是元素大小痪蝇,因此size()方法只要返回?cái)?shù)組長(zhǎng)度就可以了鄙陡。

三、總結(jié)

Vector和CopyOnWriteArrayList都是線程安全的List躏啰,底層都是數(shù)組實(shí)現(xiàn)的趁矾,Vector的每個(gè)方法都進(jìn)行了加鎖,而CopyOnWriteArrayList的讀操作是不加鎖的给僵,因此CopyOnWriteArrayList的讀性能遠(yuǎn)高于Vector毫捣,Vector每次擴(kuò)容的大小都是原來(lái)數(shù)組大小的2倍,而CopyOnWriteArrayList不需要擴(kuò)容帝际,通過(guò)COW思想就能使數(shù)組容量滿足要求蔓同。兩個(gè)集合都是先了RandomAccess接口,支持隨機(jī)讀取蹲诀,因此更加推薦使用for循環(huán)進(jìn)行遍歷斑粱。在開(kāi)發(fā)中,讀操作會(huì)遠(yuǎn)遠(yuǎn)多于其他操作脯爪,因此使用CopyOnWriteArrayList集合效率更高则北。

原文鏈接:https://www.cnblogs.com/zhixie/p/14200705.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矿微,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子尚揣,更是在濱河造成了極大的恐慌涌矢,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件快骗,死亡現(xiàn)場(chǎng)離奇詭異娜庇,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)方篮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)名秀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恭取,你說(shuō)我怎么就攤上這事泰偿。” “怎么了蜈垮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵耗跛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我攒发,道長(zhǎng)调塌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任惠猿,我火速辦了婚禮羔砾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偶妖。我一直安慰自己姜凄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布趾访。 她就那樣靜靜地躺著态秧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扼鞋。 梳的紋絲不亂的頭發(fā)上申鱼,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音云头,去河邊找鬼捐友。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溃槐,可吹牛的內(nèi)容都是我干的匣砖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脆粥!你這毒婦竟也來(lái)了砌溺?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤变隔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蟹倾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匣缘,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年鲜棠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肌厨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豁陆,死狀恐怖柑爸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盒音,我是刑警寧澤表鳍,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站祥诽,受9級(jí)特大地震影響譬圣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雄坪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一厘熟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧维哈,春花似錦绳姨、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谒亦,卻和暖如春竭宰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背份招。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工切揭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锁摔。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓廓旬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子孕豹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容