數(shù)據(jù)結(jié)構(gòu)(順序表)的應(yīng)用—— java并發(fā)容器之CopyOnWriteArrayList

Copy-On-Write簡稱COW腹缩,是一種用于程序設(shè)計中的優(yōu)化策略。其基本思路是空扎,從一開始大家都在共享同一個內(nèi)容藏鹊,當(dāng)某個人想要修改這個內(nèi)容的時候,才會真正把內(nèi)容Copy出去形成一個新的內(nèi)容然后再改转锈,這是一種延時懶惰策略盘寡。從JDK1.5開始Java并發(fā)包里提供了兩個使用CopyOnWrite機(jī)制實(shí)現(xiàn)的并發(fā)容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用撮慨,可以在非常多的并發(fā)場景中使用到竿痰。

(一)什么是CopyOnWrite容器脆粥?

CopyOnWrite容器即寫時復(fù)制的容器。通俗的理解是當(dāng)我們往一個容器添加元素的時候影涉,不直接往當(dāng)前容器添加变隔,而是先將當(dāng)前容器進(jìn)行Copy,復(fù)制出一個新的容器蟹倾,然后新的容器里添加元素匣缘,添加完元素之后,再將原容器的引用指向新的容器鲜棠。這樣做的好處是我們可以對CopyOnWrite容器進(jìn)行并發(fā)的讀肌厨,而不需要加鎖,因?yàn)楫?dāng)前容器不會添加任何元素豁陆。所以CopyOnWrite容器也是一種讀寫分離的思想柑爸,讀和寫不同的容器。

(二)CopyOnWriteArrayList如何做到線程安全的盒音?

CopyOnWriteArrayList使用了一種叫寫時復(fù)制的方法表鳍,當(dāng)有新元素添加到CopyOnWriteArrayList時,先從原有的數(shù)組中拷貝一份出來里逆,然后在新的數(shù)組做寫操作进胯,寫完之后,再將原來的數(shù)組引用指向到新數(shù)組原押。

當(dāng)有新元素加入的時候,如下圖偎血,創(chuàng)建新數(shù)組诸衔,并往新數(shù)組中加入一個新元素,這個時候,array這個引用仍然是指向原數(shù)組的颇玷。

當(dāng)元素在新數(shù)組添加成功后笨农,將array這個引用指向新數(shù)組。

(三)CopyOnWriteArrayList源碼分析

1.繼承與接口的關(guān)系關(guān)系帖渠,由此我們可以看出CopyOnWriteArrayList其實(shí)和一般的List集合實(shí)現(xiàn)類一樣都實(shí)現(xiàn)了基本的接口谒亦。

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable 

2.屬性字段

 private transient volatile Object[] elements;

volatile原理:

Java語言提供了一種稍弱的同步機(jī)制,即volatile變量空郊,用來確保將變量的更新操作通知到其他線程份招。當(dāng)把變量聲明為volatile類型后,編譯器與運(yùn)行時都會注意到這個變量是共享的狞甚,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序锁摔。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值哼审。

在訪問volatile變量時不會執(zhí)行加鎖操作谐腰,因此也就不會使執(zhí)行線程阻塞孕豹,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級的同步機(jī)制。

當(dāng)對非 volatile 變量進(jìn)行讀寫的時候十气,每個線程先從內(nèi)存拷貝變量到CPU緩存中励背。如果計算機(jī)有多個CPU,每個線程可能在不同的CPU上被處理砸西,這意味著每個線程可以拷貝到不同的 CPU cache 中叶眉。而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內(nèi)存中讀籍胯,跳過 CPU cache 這一步竟闪。

volatile 性能:

volatile 的讀性能消耗與普通變量幾乎相同,但是寫操作稍慢杖狼,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行炼蛤。

3.構(gòu)造函數(shù)

public CopyOnWriteArrayList() {
    elements = EmptyArray.OBJECT;  //創(chuàng)建一個Object類型的空表
}

 //將集合c復(fù)制到CopyOnWriteArrayList中
@SuppressWarnings("unchecked")  
public CopyOnWriteArrayList(Collection<? extends E> collection) {
    this((E[]) collection.toArray());
}

  //將傳入的數(shù)組復(fù)制到CopyOnWriteArrayList中
 public CopyOnWriteArrayList(E[] array) {
    this.elements = Arrays.copyOf(array, array.length, Object[].class);
}

4.增刪改查操作
(1)添加操作

public synchronized boolean add(E e) {
    Object[] newElements = new Object[elements.length + 1];  //這里是重新構(gòu)造了一個新的數(shù)組
    System.arraycopy(elements, 0, newElements, 0, elements.length);
    newElements[elements.length] = e;
    elements = newElements;    //然后再把新的數(shù)組復(fù)制給原來的數(shù)組
    return true;
}

CopyOnWriteArrayList的整個add操作都是在鎖的保護(hù)下進(jìn)行的。 這樣做是為了避免在多線程并發(fā)add的時候蝶涩,復(fù)制出多個副本出來,把數(shù)據(jù)搞亂了理朋。下面的幾個add方法也是同理。

public synchronized void add(int index, E e) {
    Object[] newElements = new Object[elements.length + 1];
    System.arraycopy(elements, 0, newElements, 0, index);
    newElements[index] = e;
    System.arraycopy(elements, index, newElements, index + 1, elements.length - index);
    elements = newElements;
}

public synchronized boolean addAll(Collection<? extends E> collection) {
    return addAll(elements.length, collection);
}

public synchronized boolean addAll(int index, Collection<? extends E> collection) {
    Object[] toAdd = collection.toArray();
    Object[] newElements = new Object[elements.length + toAdd.length];
    System.arraycopy(elements, 0, newElements, 0, index);
    System.arraycopy(toAdd, 0, newElements, index, toAdd.length);
    System.arraycopy(elements, index,
            newElements, index + toAdd.length, elements.length - index);
    elements = newElements;
    return toAdd.length > 0;
}

由于所有的寫操作都是在新數(shù)組進(jìn)行的绿聘,這個時候如果有線程并發(fā)的寫嗽上,則通過鎖來控制,如果有線程并發(fā)的讀熄攘,則分幾種情況:
1兽愤、如果寫操作未完成,那么直接讀取原數(shù)組的數(shù)據(jù)挪圾;
2浅萧、如果寫操作完成,但是引用還未指向新數(shù)組哲思,那么也是讀取原數(shù)組數(shù)據(jù)洼畅;
3、如果寫操作完成棚赔,并且引用已經(jīng)指向了新的數(shù)組帝簇,那么直接從新數(shù)組中讀取數(shù)據(jù)。

可見靠益,CopyOnWriteArrayList的讀操作是可以不用加鎖的丧肴。

(2)刪除操作,也是開辟另一個數(shù)組來操作

public synchronized E remove(int index) {
    @SuppressWarnings("unchecked")
    E removed = (E) elements[index];
    removeRange(index, index + 1);
    return removed;
}

private void removeRange(int from, int to) {
Object[] newElements = new Object[elements.length - (to - from)];
System.arraycopy(elements, 0, newElements, 0, from);
System.arraycopy(elements, to, newElements, from, elements.length - to);
elements = newElements;
}

public synchronized boolean remove(Object o) {
    int index = indexOf(o);
    if (index == -1) {
        return false;
    }
    remove(index);
    return true;
}

public synchronized boolean removeAll(Collection<?> collection) {
    return removeOrRetain(collection, false, 0, elements.length) != 0;
}

public synchronized boolean retainAll(Collection<?> collection) {
    return removeOrRetain(collection, true, 0, elements.length) != 0;
}

(3)修改操作

public synchronized E set(int index, E e) {
    Object[] newElements = elements.clone();
    @SuppressWarnings("unchecked")
    E result = (E) newElements[index];
    newElements[index] = e;
    elements = newElements;
    return result;
}

(4)讀取操作

@SuppressWarnings("unchecked")
public E get(int index) {
    return (E) elements[index];
}

讀的時候不需要加鎖捆毫,如果讀的時候有多個線程正在向CopyOnWriteArrayList添加數(shù)據(jù)闪湾,讀還是會讀到舊的數(shù)據(jù),因?yàn)閷懙臅r候不會鎖住舊的CopyOnWriteArrayList绩卤。

(5)其他的集合方法基本和ArrayList一樣了途样,就不在繼續(xù)分析了江醇。

通過上面的分析,CopyOnWriteArrayList 有幾個缺點(diǎn):
1何暇、由于寫操作的時候陶夜,存在內(nèi)存占用的問題,因?yàn)槊看螌θ萜鹘Y(jié)構(gòu)進(jìn)行修改的時候都要對容器進(jìn)行復(fù)制裆站,這么一來我們就有了舊有對象和新入的對象条辟,會占用兩份內(nèi)存。如果對象占用的內(nèi)存較大宏胯,就會引發(fā)頻繁的垃圾回收行為羽嫡,降低性能;

2肩袍、不能用于實(shí)時讀的場景杭棵,像拷貝數(shù)組、新增元素都需要時間氛赐,所以調(diào)用一個set操作后魂爪,讀取到數(shù)據(jù)可能還是舊的,雖然CopyOnWriteArrayList 能做到最終一致性,但是還是沒法滿足實(shí)時性要求;

CopyOnWriteArrayList 合適讀多寫少的場景艰管,不過這類慎用滓侍。因?yàn)檎l也沒法保證CopyOnWriteArrayList 到底要放置多少數(shù)據(jù),萬一數(shù)據(jù)稍微有點(diǎn)多牲芋,每次add/set都要重新復(fù)制數(shù)組撩笆,這個代價實(shí)在太高昂了。在高性能的互聯(lián)網(wǎng)應(yīng)用中缸浦,這種操作分分鐘引起故障浇衬。

CopyOnWriteArrayList透露的思想,如上面的分析CopyOnWriteArrayList表達(dá)的一些思想:
1餐济、讀寫分離,讀和寫分開
2胆剧、最終一致性 (CopyOnWrite只能保證數(shù)據(jù)最終的一致性絮姆,不能保證數(shù)據(jù)的實(shí)時一致性。)
3秩霍、使用另外開辟空間的思路篙悯,來解決并發(fā)沖突.

CopyOnWriteArrayList對我們還說還是了解有這么一個東西存在而已,一般Android都不會使用铃绒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸽照,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子颠悬,更是在濱河造成了極大的恐慌矮燎,老刑警劉巖定血,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诞外,居然都是意外死亡澜沟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門峡谊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茫虽,“玉大人,你說我怎么就攤上這事既们”粑觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵啥纸,是天一觀的道長号杏。 經(jīng)常有香客問我,道長脾拆,這世上最難降的妖魔是什么馒索? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮名船,結(jié)果婚禮上绰上,老公的妹妹穿的比我還像新娘。我一直安慰自己渠驼,他們只是感情好蜈块,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迷扇,像睡著了一般百揭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜓席,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天器一,我揣著相機(jī)與錄音,去河邊找鬼厨内。 笑死祈秕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的雏胃。 我是一名探鬼主播请毛,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瞭亮!你這毒婦竟也來了方仿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仙蚜,沒想到半個月后此洲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鳍征,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年黍翎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艳丛。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡匣掸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氮双,到底是詐尸還是另有隱情碰酝,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布戴差,位于F島的核電站送爸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏暖释。R本人自食惡果不足惜袭厂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望球匕。 院中可真熱鬧纹磺,春花似錦、人聲如沸亮曹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽照卦。三九已至式矫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間役耕,已是汗流浹背采转。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞬痘,地道東北人氏义。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像图云,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子邻邮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

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

  • 從三月份找實(shí)習(xí)到現(xiàn)在竣况,面了一些公司,掛了不少,但最終還是拿到小米丹泉、百度情萤、阿里、京東摹恨、新浪筋岛、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,184評論 11 349
  • Java SE 基礎(chǔ): 封裝晒哄、繼承睁宰、多態(tài) 封裝: 概念:就是把對象的屬性和操作(或服務(wù))結(jié)合為一個獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,099評論 0 8
  • 一個簡單的單例示例 單例模式可能是大家經(jīng)常接觸和使用的一個設(shè)計模式寝凌,你可能會這么寫 publicclassUnsa...
    Martin說閱讀 2,211評論 0 6
  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的柒傻,后來想想還是整...
    coder_pig閱讀 1,629評論 2 17
  • 用書中的話說“納蘭這一世,在詞中稱帝较木,卻做了情感的奴红符,做了生命的奴》フ” 他出生在寒梅競開的臘月预侯,于納蘭世家,一個縱...
    SHOUT72817閱讀 615評論 0 0