如何實現(xiàn)一個線程安全的數(shù)據(jù)結(jié)構(gòu)

一. 如何實現(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é):

  1. HashTable是線程安全類铸董;通過對其方法函數(shù)進(jìn)行synchronized修飾實現(xiàn)其特性,效率低下肴沫,目前已被jdk廢棄粟害,不再推薦使用。
  2. 在多線程環(huán)境下颤芬,我們常用ConcurrentHashMap在需要保證數(shù)據(jù)安全的場景中去替換HashMap悲幅;此外ConcurrentHashMap也有不錯的性能表現(xiàn)
  3. CopyOnWriteArrayList類是一個線程安全的List接口的實現(xiàn),在高并發(fā)的情況下站蝠,可以提供高性能的并發(fā)讀取汰具,并且保證讀取的內(nèi)容一定是正確的,這對于讀操作遠(yuǎn)遠(yuǎn)多于寫操作的應(yīng)用非常適合沉衣。
  4. CopyOnWriteArraySet是對CopyOnWriteArrayList使用了裝飾模式后的具體實現(xiàn)郁副,可理解為線程安全的Set。
  5. ConcurrentLinkedQueue應(yīng)該算是在高并發(fā)環(huán)境中性能最好的隊列豌习;在多線程的隊列應(yīng)用場景中存谎,強(qiáng)烈推薦使用。
  6. Vector中的操作是線程安全的肥隆,它是利用synchronized同步鎖機(jī)制進(jìn)行實現(xiàn)既荚,其實現(xiàn)方式與HashTable類似。
  7. StringBuffer與StringBuilder常用于字符串拼接栋艳;前者線程安全恰聘,后者不是線程安全的;在多線程環(huán)境中下,考慮數(shù)據(jù)安全使用前者晴叨,否則使用后者凿宾。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兼蕊,隨后出現(xiàn)的幾起案子初厚,更是在濱河造成了極大的恐慌,老刑警劉巖孙技,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件产禾,死亡現(xiàn)場離奇詭異,居然都是意外死亡牵啦,警方通過查閱死者的電腦和手機(jī)亚情,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哈雏,“玉大人楞件,你說我怎么就攤上這事∩” “怎么了履因?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盹愚。 經(jīng)常有香客問我栅迄,道長,這世上最難降的妖魔是什么皆怕? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任毅舆,我火速辦了婚禮,結(jié)果婚禮上愈腾,老公的妹妹穿的比我還像新娘憋活。我一直安慰自己,他們只是感情好虱黄,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布悦即。 她就那樣靜靜地躺著,像睡著了一般橱乱。 火紅的嫁衣襯著肌膚如雪辜梳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天泳叠,我揣著相機(jī)與錄音作瞄,去河邊找鬼。 笑死危纫,一個胖子當(dāng)著我的面吹牛宗挥,可吹牛的內(nèi)容都是我干的乌庶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼契耿,長吁一口氣:“原來是場噩夢啊……” “哼瞒大!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宵喂,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤糠赦,失蹤者是張志新(化名)和其女友劉穎会傲,沒想到半個月后锅棕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡淌山,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年裸燎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泼疑。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡德绿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出退渗,到底是詐尸還是另有隱情移稳,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布会油,位于F島的核電站个粱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翻翩。R本人自食惡果不足惜都许,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫂冻。 院中可真熱鬧胶征,春花似錦、人聲如沸桨仿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽服傍。三九已至钱雷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伴嗡,已是汗流浹背急波。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留瘪校,地道東北人澄暮。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓名段,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泣懊。 傳聞我的和親對象是個殘疾皇子伸辟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

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