多線程并發(fā)下的線程安全的集合類的使用

1浦妄、概念簡介:

線程安全:就是當(dāng)多線程訪問時呢灶,采用了加鎖的機(jī)制吴超;即當(dāng)一個線程訪問該類的某個數(shù)據(jù)時,會對這個數(shù)據(jù)進(jìn)行保護(hù)鸯乃,其他線程不能對其訪問鲸阻,直到該線程讀取完之后,其他線程才可以使用缨睡。防止出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)被污染的情況鸟悴。

線程不安全:就是不提供數(shù)據(jù)訪問時的數(shù)據(jù)保護(hù),多個線程能夠同時操作某個數(shù)據(jù)奖年,從而出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染的情況细诸。

對于線程不安全的問題,一般會使用synchronized關(guān)鍵字加鎖同步控制陋守。

線程安全 工作原理: jvm中有一個main memory對象震贵,每一個線程也有自己的working memory,一個線程對于一個變量variable進(jìn)行操作的時候水评, 都需要在自己的working memory里創(chuàng)建一個copy,操作完之后再寫入main memory猩系。?

當(dāng)多個線程操作同一個變量variable,就可能出現(xiàn)不可預(yù)知的結(jié)果中燥。?

而用synchronized的關(guān)鍵是建立一個監(jiān)控monitor寇甸,這個monitor可以是要修改的變量,也可以是其他自己認(rèn)為合適的對象(方法),然后通過給這個monitor加鎖來實現(xiàn)線程安全拿霉,每個線程在獲得這個鎖之后吟秩,要執(zhí)行完加載load到working memory 到 use && 指派assign 到 存儲store 再到 main memory的過程。才會釋放它得到的鎖绽淘。這樣就實現(xiàn)了所謂的線程安全涵防。

1、?線程的安全控制有三個級別

?? JVM 級別收恢。大多數(shù)現(xiàn)代處理器對并發(fā)對 某一硬件級別提供支持武学,通常以 compare-and-swap (CAS)指令形式祭往。CAS 是一種低級別的伦意、細(xì)粒度的技術(shù),它允許多個線程更新一個內(nèi)存位置硼补,同時能夠檢測其他線程的沖突并進(jìn)行恢復(fù)驮肉。它是許多高性能并發(fā)算法的基礎(chǔ)。在 JDK 5.0 之前已骇,Java 語言中用于協(xié)調(diào)線程之間的訪問的惟一原語是同步离钝,同步是更重量級和粗粒度的。公開 CAS 可以開發(fā)高度可伸縮的并發(fā) Java 類褪储。

?? 低級實用程序類 -- 鎖定和原子類卵渴。使用 CAS 作為并發(fā)原語,ReentrantLock 類提供與 synchronized 原語相同的鎖定和內(nèi)存語義鲤竹,然而這樣可以更好地控制鎖定(如計時的鎖定等待浪读、鎖定輪詢和可中斷的鎖定等待)和提供更好的可伸縮性(競爭時的高性能)。大多數(shù)開發(fā)人員將不再直接使用 ReentrantLock 類辛藻,而是使用在 ReentrantLock 類上構(gòu)建的高級類碘橘。

?? 高級實用程序類。這些類實現(xiàn)并發(fā)構(gòu)建塊吱肌,每個計算機(jī)科學(xué)課本中都會講述這些類 -- 信號痘拆、互斥、閂鎖氮墨、屏障纺蛆、交換程序、線程池和線程安全集合類等规揪。大部分開發(fā)人員都可以在應(yīng)用程序中用這些類桥氏,來替換許多同步、 wait() 和 notify() 的使用粒褒,從而提高性能识颊、可讀性和正確性。

2、常見的線程安全操作列舉幾個

①加鎖同步synchronizedLock等

?②wait() notify()線程調(diào)度 已實現(xiàn)執(zhí)行的同步

③ThreadLocal局部變量 ?每一個線程都有一份數(shù)據(jù)

?④Semaphore 信號量

?⑤volatile 保證一個變量的線程安全


2祥款、常見的數(shù)據(jù)集合簡介

常見并且常用的數(shù)據(jù)集合有map清笨,hashmap,hashSet刃跛,TreeMap抠艾, TreeSet, List桨昙, ArrayList检号, LinkedList, StringBuilder

線程安全的數(shù)據(jù)集合有:Vector蛙酪, HashTable齐苛, StringBuffer,? ConcurrentHashMap桂塞,?Collections.synchronizedList凹蜂,?CopyOnWriteArrayList

相關(guān)集合對象的比較:

Vector、ArrayList阁危、LinkedList玛痊, Collections.synchronizedList, CopyOnWriteArrayList:?

1狂打、Vector:

Vector與ArrayList一樣擂煞,也是通過數(shù)組實現(xiàn)的,不同的是它支持線程的同步趴乡,即某一時刻只有一個線程能夠?qū)慥ector对省,避免多線程同時寫而引起的不一致性,但實現(xiàn)同步需要很高的花費浙宜,因此官辽,訪問它比訪問ArrayList慢。?

2粟瞬、ArrayList:?

a. 當(dāng)操作是在一列數(shù)據(jù)的后面添加數(shù)據(jù)而不是在前面或者中間同仆,并需要隨機(jī)地訪問其中的元素時,使用ArrayList性能比較好裙品。?

b. ArrayList是最常用的List實現(xiàn)類俗批,內(nèi)部是通過數(shù)組實現(xiàn)的,它允許對元素進(jìn)行快速隨機(jī)訪問市怎。數(shù)組的缺點是每個元素之間不能有間隔岁忘,當(dāng)數(shù)組大小不滿足時需要增加存儲能力,就要講已經(jīng)有數(shù)組的數(shù)據(jù)復(fù)制到新的存儲空間中区匠。當(dāng)從ArrayList的中間位置插入或者刪除元素時干像,需要對數(shù)組進(jìn)行復(fù)制帅腌、移動、代價比較高麻汰。因此速客,它適合隨機(jī)查找和遍歷腺律,不適合插入和刪除对妄。?

3、LinkedList:?

a. 當(dāng)對一列數(shù)據(jù)的前面或者中間執(zhí)行添加或者刪除操作時赛不,并且按照順序訪問其中的元素時位喂,要使用LinkedList浪耘。?

b. LinkedList是用鏈表結(jié)構(gòu)存儲數(shù)據(jù)的,很適合數(shù)據(jù)的動態(tài)插入和刪除塑崖,隨機(jī)訪問和遍歷速度比較慢七冲。另外,他還提供了List接口中沒有定義的方法弃舒,專門用于操作表頭和表尾元素癞埠,可以當(dāng)作堆棧状原、隊列和雙向隊列使用聋呢。

  ? Vector和ArrayList在使用上非常相似,都可以用來表示一組數(shù)量可變的對象應(yīng)用的集合颠区,并且可以隨機(jī)的訪問其中的元素削锰。?


HashTable、HashMap毕莱、HashSet:?

HashTable和HashMap采用的存儲機(jī)制是一樣的器贩,不同的是:?

1、HashMap:

a. 采用數(shù)組方式存儲key-value構(gòu)成的Entry對象朋截,無容量限制蛹稍;?

b. 基于key hash查找Entry對象存放到數(shù)組的位置,對于hash沖突采用鏈表的方式去解決部服;?

c. 在插入元素時唆姐,可能會擴(kuò)大數(shù)組的容量,在擴(kuò)大容量時須要重新計算hash廓八,并復(fù)制對象到新的數(shù)組中奉芦;?

d. 是非線程安全的;?

e. 遍歷使用的是Iterator迭代器剧蹂;

2声功、HashTable:?

a. 是線程安全的;?

b. 無論是key還是value都不允許有null值的存在宠叼;在HashTable中調(diào)用Put方法時先巴,如果key為null,直接拋出NullPointerException異常;?

c. 遍歷使用的是Enumeration列舉伸蚯;

3醋闭、HashSet:?

a. 基于HashMap實現(xiàn),無容量限制朝卒;?

b. 是非線程安全的证逻;?

c. 不保證數(shù)據(jù)的有序;


TreeSet抗斤、TreeMap:

TreeSet和TreeMap都是完全基于Map來實現(xiàn)的囚企,并且都不支持get(index)來獲取指定位置的元素,需要遍歷來獲取瑞眼。另外龙宏,TreeSet還提供了一些排序方面的支持,例如傳入Comparator實現(xiàn)伤疙、descendingSet以及descendingIterator等银酗。?

1、TreeSet:?

a. 基于TreeMap實現(xiàn)的徒像,支持排序黍特;?

b. 是非線程安全的;

2锯蛀、TreeMap:?

a. 典型的基于紅黑樹的Map實現(xiàn)灭衷,因此它要求一定要有key比較的方法,要么傳入Comparator比較器實現(xiàn)旁涤,要么key對象實現(xiàn)Comparator接口翔曲;?

b. 是非線程安全的;


StringBuffer和StringBulider:?

StringBuilder與StringBuffer都繼承自AbstractStringBuilder類劈愚,在AbstractStringBuilder中也是使用字符數(shù)組保存字符串瞳遍。

  ? 1、在執(zhí)行速度方面的比較:StringBuilder > StringBuffer 菌羽;?

  ? 2掠械、他們都是字符串變量,是可改變的對象算凿,每當(dāng)我們用它們對字符串做操作時份蝴,實際上是在一個對象上操作的,不像String一樣創(chuàng)建一些對象進(jìn)行操作氓轰,所以速度快婚夫;?

 ?  3、 StringBuilder:線程非安全的署鸡;?

  ? 4案糙、StringBuffer:線程安全的限嫌;

對于String、StringBuffer和StringBulider三者使用的總結(jié):

   1.如果要操作少量的數(shù)據(jù)用 = String?

   2.單線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuilder?

   3.多線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuffer

vector,hashtable是在Java1.0就引入的集合时捌,兩個都是線程安全的怒医,但是現(xiàn)在已很少使用,原因就是內(nèi)部實現(xiàn)的線程安全太消耗資源奢讨,因此新的安全的高效的數(shù)據(jù)集合出現(xiàn)了稚叹,這就是ConcurrentHashMap,?Collections.synchronizedList拿诸,?CopyOnWriteArrayList扒袖。

為什么我們想要新的線程安全的List類?為什么會出現(xiàn)CopyOnWriteArrayList亩码?

簡單的答案是與迭代和并發(fā)修改之間的交互有關(guān)季率。使用 Vector 或使用同步的 List 封裝器,返回的迭代器是 fail-fast 的描沟,

這意味著如果在迭代過程中任何其他線程修改 List飒泻,迭代可能失敗。Vector 的非常普遍的應(yīng)用程序是存儲通過組件注冊的監(jiān)聽器的列表吏廉。當(dāng)發(fā)生適合的事件時泞遗,該組件將在監(jiān)聽器的列表中迭代,調(diào)用每個監(jiān)聽器迟蜜。

為了防止ConcurrentModificationException刹孔,迭代線程必須復(fù)制列表或鎖定列表,一遍進(jìn)行整體迭代娜睛,而這兩種情況都需要大量的性能成本。CopyOnWriteArrayList類通過每次添加或刪除元素時創(chuàng)建支持?jǐn)?shù)組的新副本卦睹,避免了這個問題畦戒,但是進(jìn)行中的迭代保持對創(chuàng)建迭代器時的當(dāng)前副本進(jìn)行操作。雖然復(fù)制也會有一些成本结序,但是在許多情況下障斋,迭代要比修改多得多,在這些情況系徐鹤,寫入時復(fù)制要比其他備用方法具有更好的性能和并發(fā)性垃环。

CopyOnWriteArrayList如何做到線程安全的?

CopyOnWriteArrayList使用了一種叫寫時復(fù)制的方法返敬,當(dāng)有新元素添加到CopyOnWriteArrayList時遂庄,先從原有的數(shù)組中拷貝一份出來,然后在新的數(shù)組做寫操作劲赠,寫完之后涛目,再將原來的數(shù)組引用指向到新數(shù)組秸谢。

當(dāng)有新元素加入的時候,如下圖霹肝,創(chuàng)建新數(shù)組估蹄,并往新數(shù)組中加入一個新元素,這個時候,array這個引用仍然是指向原數(shù)組的沫换。

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

CopyOnWriteArrayList的整個add操作都是在的保護(hù)下進(jìn)行的讯赏。?

這樣做是為了避免在多線程并發(fā)add的時候刊棕,復(fù)制出多個副本出來,把數(shù)據(jù)搞亂了,導(dǎo)致最終的數(shù)組數(shù)據(jù)不是我們期望的待逞。

CopyOnWriteArrayList的add操作的源代碼如下:

public boolean add(E e) {

? ? //1甥角、先加鎖? ? final ReentrantLock lock = this.lock;

? ? lock.lock();

? ? try {

? ? ? ? Object[] elements = getArray();

? ? ? ? int len = elements.length;

? ? ? ? //2、拷貝數(shù)組? ? ? ? Object[] newElements = Arrays.copyOf(elements, len + 1);

? ? ? ? //3识樱、將元素加入到新數(shù)組中? ? ? ? newElements[len] = e;

? ? ? ? //4嗤无、將array引用指向到新數(shù)組? ? ? ? setArray(newElements);

? ? ? ? return true;

? ? } finally {

? ? ? //5、解鎖? ? ? ? lock.unlock();

? ? }

}

由于所有的寫操作都是在新數(shù)組進(jìn)行的怜庸,這個時候如果有線程并發(fā)的寫当犯,則通過鎖來控制,如果有線程并發(fā)的讀割疾,則分幾種情況:?

1嚎卫、如果寫操作未完成,那么直接讀取原數(shù)組的數(shù)據(jù)宏榕;?

2拓诸、如果寫操作完成,但是引用還未指向新數(shù)組麻昼,那么也是讀取原數(shù)組數(shù)據(jù)奠支;?

3、如果寫操作完成抚芦,并且引用已經(jīng)指向了新的數(shù)組倍谜,那么直接從新數(shù)組中讀取數(shù)據(jù)。

可見叉抡,CopyOnWriteArrayList的讀操作是可以不用加鎖的尔崔。

CopyOnWriteArrayList的使用場景

通過上面的分析,CopyOnWriteArrayList?有幾個缺點:?

1褥民、由于寫操作的時候季春,需要拷貝數(shù)組,會消耗內(nèi)存轴捎,如果原數(shù)組的內(nèi)容比較多的情況下鹤盒,可能導(dǎo)致young gc或者full gc

2蚕脏、不能用于實時讀的場景,像拷貝數(shù)組侦锯、新增元素都需要時間驼鞭,所以調(diào)用一個set操作后,讀取到數(shù)據(jù)可能還是舊的,雖然CopyOnWriteArrayList?能做到最終一致性,但是還是沒法滿足實時性要求尺碰;

CopyOnWriteArrayList?合適讀多寫少的場景挣棕,不過這類慎用?

因為誰也沒法保證CopyOnWriteArrayList?到底要放置多少數(shù)據(jù),萬一數(shù)據(jù)稍微有點多亲桥,每次add/set都要重新復(fù)制數(shù)組洛心,這個代價實在太高昂了。在高性能的互聯(lián)網(wǎng)應(yīng)用中题篷,這種操作分分鐘引起故障词身。

CopyOnWriteArrayList透露的思想

如上面的分析CopyOnWriteArrayList表達(dá)的一些思想:?

1、讀寫分離番枚,讀和寫分開?

2法严、最終一致性?

3、使用另外開辟空間的思路葫笼,來解決并發(fā)沖突

Collections.synchronizedList

CopyOnWriteArrayList和Collections.synchronizedList是實現(xiàn)線程安全的列表的兩種方式深啤。兩種實現(xiàn)方式分別針對不同情況有不同的性能表現(xiàn),其中CopyOnWriteArrayList的寫操作性能較差路星,而多線程的讀操作性能較好溯街。而Collections.synchronizedList的寫操作性能比CopyOnWriteArrayList在多線程操作的情況下要好很多,而讀操作因為是采用了synchronized關(guān)鍵字的方式洋丐,其讀操作性能并不如CopyOnWriteArrayList呈昔。因此在不同的應(yīng)用場景下,應(yīng)該選擇不同的多線程安全實現(xiàn)類垫挨。

?Collections.synchronizedList的源碼可知韩肝,其實現(xiàn)線程安全的方式是建立了list的包裝類,代碼如下:

public?static??List?synchronizedList(List?list)?{??

return?(list?instanceof?RandomAccess????

new?SynchronizedRandomAccessList(list)?:??

new?SynchronizedList(list));//根據(jù)不同的list類型最終實現(xiàn)不同的包裝類九榔。??

???}?

其中,SynchronizedList對部分操作加上了synchronized關(guān)鍵字以保證線程安全涡相。但其iterator()操作還不是線程安全的哲泊。部分SynchronizedList的代碼如下:

public?E?get(int?index)?{??

synchronized(mutex)?{return?list.get(index);}??

????????}??

public?E?set(int?index,?E?element)?{??

synchronized(mutex)?{return?list.set(index,?element);}??

????????}??

public?void?add(int?index,?E?element)?{??

synchronized(mutex)?{list.add(index,?element);}??

????????}??

public?ListIterator?listIterator()?{??

return?list.listIterator();?//?Must?be?manually?synched?by?user?需要用戶保證同步,否則仍然可能拋出ConcurrentModificationException??

????????}??


public?ListIterator?listIterator(int?index)?{??

return?list.listIterator(index);?//?Must?be?manually?synched?by?user?需要用戶保證同步催蝗,否則仍然可能拋出ConcurrentModificationException??

????????}

?寫操作:在線程數(shù)目增加時CopyOnWriteArrayList的寫操作性能下降非常嚴(yán)重切威,而Collections.synchronizedList雖然有性能的降低,但下降并不明顯丙号。

? ? ? ? 讀操作:在多線程進(jìn)行讀時先朦,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低缰冤,但是Collections.synchronizedList的性能降低更加顯著。



轉(zhuǎn)載自線程并發(fā)線程安全介紹及java.util.concurrent包下類介紹 - CSDN博客,

Java中各種集合(字符串類)的線程安全性T骸C藿! - 鴻燕藏鋒 - 博客園,

線程安全的CopyOnWriteArrayList介紹 - CSDN博客,

CopyOnWriteArrayList與Collections.synchronizedList的性能對比 - CSDN博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刺彩,一起剝皮案震驚了整個濱河市迷郑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌创倔,老刑警劉巖嗡害,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畦攘,居然都是意外死亡霸妹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門知押,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叹螟,“玉大人,你說我怎么就攤上這事朗徊∈籽” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵爷恳,是天一觀的道長有缆。 經(jīng)常有香客問我,道長温亲,這世上最難降的妖魔是什么棚壁? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮栈虚,結(jié)果婚禮上袖外,老公的妹妹穿的比我還像新娘。我一直安慰自己魂务,他們只是感情好曼验,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粘姜,像睡著了一般鬓照。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孤紧,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天豺裆,我揣著相機(jī)與錄音,去河邊找鬼号显。 笑死臭猜,一個胖子當(dāng)著我的面吹牛躺酒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蔑歌,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼羹应,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丐膝?” 一聲冷哼從身側(cè)響起量愧,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帅矗,沒想到半個月后偎肃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡浑此,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年累颂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凛俱。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡紊馏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒲犬,到底是詐尸還是另有隱情朱监,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布原叮,位于F島的核電站赫编,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奋隶。R本人自食惡果不足惜擂送,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唯欣。 院中可真熱鬧嘹吨,春花似錦、人聲如沸境氢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萍聊。三九已至匹厘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脐区,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工她按, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留牛隅,地道東北人炕柔。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像媒佣,于是被迫代替她去往敵國和親匕累。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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