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博客