一. 如何實現(xiàn)一個線程安全的數(shù)據(jù)結(jié)構(gòu)
1.餓漢模式
public class Singletan {
private static Singletan instance = new Singletan();
public Singletan(){}
public static Singletan getInstance() {
return instance;
}
}
2.靜態(tài)內(nèi)部類
//加載內(nèi)部類SingletanHolder枫慷,從而實例化instance
public class Singletan {
private static class SingletanHolder {
private static final Singletan INSTANCE = new Singletan();
}
public Singletan(){}
public static final Singletan getInstance() {
return SingletanHolder.INSTANCE;
}
}
3.CAS:Compare and Swap(比較和交換)
- 樂觀鎖让蕾,無鎖算法。CAS有3個參數(shù):內(nèi)存值V或听、舊值A(chǔ)探孝、要修改的新值B,當(dāng)且僅當(dāng)舊值 A 和內(nèi)存值 V 相同時誉裆,才將內(nèi)存值 V 修改為 B顿颅,否則會嘗試重新獲取 V 的當(dāng)前值,重新比較足丢。
- 優(yōu)點:沒有線程切換和阻塞的額外開銷粱腻,支持較大并行度绍填。
- 缺點:①在并發(fā)量較高時,如果許多線程反復(fù)嘗試去更新一個變量栖疑,卻又一直更新失敗讨永,會消耗很多CPU資源。②銀行取款 ABA 問題遇革,所以不僅要比較舊值和內(nèi)存值卿闹,還要比較變量的版本號是否一致,只有一致才進(jìn)行操作萝快。
銀行取款A(yù)BA問題:假如賬戶開始有100元锻霎,在取款機(jī)上取走50,取款機(jī)出現(xiàn)問題一共提交了兩次請求(線程1揪漩,線程2)旋恼,第二次請求(線程2)在執(zhí)行時因為某種原因被阻塞了,這時候有人往你的賬戶打了50元奄容,線程2恢復(fù)了可執(zhí)行狀態(tài)冰更,這個時候就會出現(xiàn)問題,原本線程2應(yīng)該執(zhí)行失敗的昂勒,但是比較后仍然與舊值一致蜀细,這樣就造成了賬戶實際上扣款了兩次!
二.Java中滿足線程安全的數(shù)據(jù)結(jié)構(gòu)
所謂 線程安全 就是:一段操縱共享數(shù)據(jù)的代碼能夠保證在同一時間內(nèi)被多個線程執(zhí)行而仍然保持其正確性的戈盈,就被稱為是線程安全的奠衔。
線程安全是保證執(zhí)行業(yè)務(wù)邏輯正確的基本前提,為此在多線程開發(fā)中塘娶,我們盡量采用能保證線程安全的數(shù)據(jù)結(jié)構(gòu)归斤。
JDK已經(jīng)為大家準(zhǔn)備好了一批好用的線程安全容器類,可以大大減少開發(fā)工作量刁岸,例如HashTable脏里,ConcurrentHashMap,CopyOnWriteArrayList难捌,CopyOnWriteArraySet膝宁,ConcurrentLinkedQueue,Vector根吁,StringBuffer等。本文主要對這些數(shù)據(jù)結(jié)構(gòu)的功能及其常見使用場景進(jìn)行說明與比較合蔽。
1击敌、HashTable
HashTable實現(xiàn)了Map接口,為此其本身也是一個散列表拴事,它存儲的內(nèi)容是基于key-value的鍵值對映射沃斤。
HashTable中的key圣蝎、value都不可以為null;具有無序特性衡瓶;由于其方法函數(shù)都是同步的(采用synchronized修飾)徘公,不會出現(xiàn)兩個線程同時對數(shù)據(jù)進(jìn)行操作的情況,因此保證了線程安全性哮针。
HashTable使用synchronized來修飾方法函數(shù)來保證線程安全关面,但是在多線程運行環(huán)境下效率表現(xiàn)非常低下。因為當(dāng)一個線程訪問HashTable的同步方法時十厢,其他線程也訪問同步方法就會粗線阻塞狀態(tài)等太。比如當(dāng)一個線程在添加數(shù)據(jù)時候,另外一個線程即使執(zhí)行獲取其他數(shù)據(jù)的操作也必須被阻塞蛮放,大大降低了程序的運行效率缩抡。
2、ConcurrentHashMap
我們知道HashMap是線程不安全的包颁,ConcurrentHashMap是HashMap的線程安全版瞻想。
但是與HashTable相比,ConcurrentHashMap不僅保證了多線程運行環(huán)境下的數(shù)據(jù)訪問安全性娩嚼,而且性能上有長足的提升内边。
ConcurrentHashMap允許多個修改操作并發(fā)運行,其原因在于使用了鎖分段技術(shù):首先講Map存放的數(shù)據(jù)分成一段一段的存儲方式,然后給每一段數(shù)據(jù)分配一把鎖佑淀,當(dāng)一個線程占用鎖訪問其中一個段的數(shù)據(jù)時诡渴,其他段的數(shù)據(jù)也能被其他線程訪問。這樣就保證了每一把鎖只是用于鎖住一部分?jǐn)?shù)據(jù)和屎,那么當(dāng)多線程訪問Map里的不同數(shù)據(jù)段的數(shù)據(jù)時,線程間就不會存在鎖競爭春瞬,從而可以有效提高并發(fā)訪問效率柴信。
上述的處理機(jī)制明顯區(qū)別于HashTable是給整體數(shù)據(jù)分配了一把鎖的處理方法。為此宽气,在多線程環(huán)境下随常,常用ConcurrentHashMap在需要保證數(shù)據(jù)安全的場景中去替換HashMap,而不會去使用HashTable萄涯,同時在最新版的JDK中已經(jīng)推薦廢棄使用HashTable绪氛。
3、CopyOnWriteArrayList
CopyOnWriteArrayList實現(xiàn)了List接口涝影,提供的數(shù)據(jù)更新操作都使用了ReentrantLock的lock()方法來加鎖枣察,unlock()方法來解鎖。
當(dāng)增加元素的時候,首先使用Arrays.copyOf()來拷貝形成新的副本序目,在副本上增加元素臂痕,然后改變原引用指向副本。讀操作不需要加鎖猿涨,而寫操作類實現(xiàn)中對其進(jìn)行了加鎖握童。因此,CopyOnWriteArrayList類是一個線程安全的List接口的實現(xiàn)叛赚,在高并發(fā)的情況下澡绩,可以提供高性能的并發(fā)讀取,并且保證讀取的內(nèi)容一定是正確的红伦,這對于讀操作遠(yuǎn)遠(yuǎn)多于寫操作的應(yīng)用非常適合(注意: 如上述更新操作會帶來較大的空間與性能開銷英古,如果更新操太過頻繁,反而不太合適使用)昙读。
4召调、CopyOnWriteArraySet
CopyOnWriteArraySet是對CopyOnWriteArrayList使用了裝飾模式后的具體實現(xiàn)。所以CopyOnWriteArrayList的實現(xiàn)機(jī)理適用于CopyOnWriteArraySet蛮浑,此處不再贅述唠叛。
Java里的List和Set的之間的特性比較結(jié)論同樣適用于CopyOnWriteArrayList與CopyOnWriteArraySet之間的比較;此外沮稚,CopyOnWriteArrayList與CopyOnWriteArraySet都是線程安全的艺沼。
5、ConcurrentLinkedQueue
ConcurrentLinkedQueue可以被看作是一個線程安全的LinkedList蕴掏,使用了非阻塞算法實現(xiàn)的一個高效障般、線程安全的并發(fā)隊列;其本質(zhì)是一個基于鏈接節(jié)點的無界線程安全隊列盛杰,它采用先進(jìn)先出的規(guī)則對節(jié)點進(jìn)行排序挽荡,當(dāng)添加一個元素時會添加到隊列的尾部;當(dāng)獲取一個元素時即供,會返回隊列頭部的元素定拟。
ConcurrentLinkedQueue應(yīng)該算是在高并發(fā)環(huán)境中性能最好的隊列,沒有之一逗嫡。
6青自、Vector
Vector通過數(shù)組保存數(shù)據(jù),繼承了Abstract驱证,實現(xiàn)了List延窜;所以,其本質(zhì)上是一個隊列雷滚。
但是和ArrayList不同需曾,Vector中的操作是線程安全的,它是利用synchronized同步鎖機(jī)制進(jìn)行實現(xiàn)祈远,其實現(xiàn)方式與HashTable類似呆万。
7、StringBuffer與StringBuilder
在Java里面车份,字符串操作應(yīng)該是最頻繁的操作了谋减,為此有必要把StringBuffer與StringBuilder兩個方法類比較一下。
首先扫沼,對于頻繁的字符串拼接操作出爹,是不推薦采用效率低下的“+”操作的。一般是采用StringBuffer與StringBuilder來實現(xiàn)上述功能缎除。但是严就,這兩者也是有區(qū)別的:前者線程安全,后者不是線程安全的器罐。
StringBuffer是通過對方法函數(shù)進(jìn)行synchronized修飾實現(xiàn)其線程安全特性梢为,實現(xiàn)方式與HashTable、Vector類似轰坊。
總結(jié):
- HashTable是線程安全類铸董;通過對其方法函數(shù)進(jìn)行synchronized修飾實現(xiàn)其特性,效率低下肴沫,目前已被jdk廢棄粟害,不再推薦使用。
- 在多線程環(huán)境下颤芬,我們常用ConcurrentHashMap在需要保證數(shù)據(jù)安全的場景中去替換HashMap悲幅;此外ConcurrentHashMap也有不錯的性能表現(xiàn)
- CopyOnWriteArrayList類是一個線程安全的List接口的實現(xiàn),在高并發(fā)的情況下站蝠,可以提供高性能的并發(fā)讀取汰具,并且保證讀取的內(nèi)容一定是正確的,這對于讀操作遠(yuǎn)遠(yuǎn)多于寫操作的應(yīng)用非常適合沉衣。
- CopyOnWriteArraySet是對CopyOnWriteArrayList使用了裝飾模式后的具體實現(xiàn)郁副,可理解為線程安全的Set。
- ConcurrentLinkedQueue應(yīng)該算是在高并發(fā)環(huán)境中性能最好的隊列豌习;在多線程的隊列應(yīng)用場景中存谎,強(qiáng)烈推薦使用。
- Vector中的操作是線程安全的肥隆,它是利用synchronized同步鎖機(jī)制進(jìn)行實現(xiàn)既荚,其實現(xiàn)方式與HashTable類似。
- StringBuffer與StringBuilder常用于字符串拼接栋艳;前者線程安全恰聘,后者不是線程安全的;在多線程環(huán)境中下,考慮數(shù)據(jù)安全使用前者晴叨,否則使用后者凿宾。