ConcurrentHashMap詳解,以及各map的相互比較

并發(fā)環(huán)境下HashMap是不安全的,容易造成并發(fā)修改異掣蠼或者死鎖現(xiàn)象
而Collections下提供的synchionizedMap雖然因?yàn)榧恿藄ynchionized而變得安全,卻也因此極大的降低了性能
由于以上情況,就使得ConcurrentHashMap應(yīng)運(yùn)而生


如何優(yōu)化Hashtable添履?

  • 通過鎖細(xì)粒度化角雷,將整鎖拆解成多個(gè)鎖進(jìn)行優(yōu)化
    將整鎖拆為十六個(gè)子鎖,一個(gè)線程每次只操作一個(gè)鎖并不干擾其他線程操作其他鎖,理論上性能提高了16倍

ConcurrentHashMap的擴(kuò)容因子SizeCtl是用volatile修飾的對(duì)其他線程可見
ConcurrentHashMap可以通過computreIfAbsent()和computre()構(gòu)建java本地緩存,降低程序計(jì)算量

ConcurrentHashMap:put方法的源碼邏輯

1.判斷Node[]數(shù)組是否初始化读跷,沒有則進(jìn)行初始化操作
2.通過hash定位數(shù)組的索引坐標(biāo)少漆,是否有Node節(jié)點(diǎn)逸嘀,如果沒有則使用CAS進(jìn)行添加(鏈表的頭節(jié)點(diǎn))枉氮,添加失敗則進(jìn)入下次循環(huán)。
3.檢查到內(nèi)部正在擴(kuò)容抬旺,就幫助它一塊擴(kuò)容弊予。(主要做數(shù)據(jù)標(biāo)記,輔助遷移等)
4.如果f=null(頭節(jié)點(diǎn)不為空),則使用synchronized鎖住元素(鏈表/紅黑二叉樹的頭元素)
??4.1如果是Node(鏈表結(jié)構(gòu))則執(zhí)行鏈表的添加操作开财。
??4.2如果是TreeNode(樹型結(jié)構(gòu))則執(zhí)行樹添加操作汉柒。
5.判斷鏈表長(zhǎng)度已經(jīng)達(dá)到臨界值8,當(dāng)然這個(gè)8是默認(rèn)值责鳍,大家也可以去做調(diào)整碾褂,當(dāng)節(jié)點(diǎn)數(shù)超過這個(gè)值就需要把鏈表轉(zhuǎn)換為樹結(jié)構(gòu)。

ConcurrentHashMap總結(jié):比起分段鎖Segment历葛,鎖拆得更細(xì)
  • 首先使用無鎖操作CAS插入頭節(jié)點(diǎn)正塌,失敗則循環(huán)重試
  • 若頭節(jié)點(diǎn)已存在,則通過synronchized嘗試獲取頭節(jié)點(diǎn)的同步鎖恤溶,再進(jìn)行操作
public V put(K key, V value) {
   return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
//onlyIfAbsent的意思是在put一個(gè)KV時(shí)传货,如果K已經(jīng)存在什么也不做則返回null
//如果不存在則put操作后返回V值
final V putVal(K key, V value, boolean onlyIfAbsent) {
   //ConcurrentHashMap中是不能有空K或空V的
   if (key == null || value == null) throw new NullPointerException();
   //hash算法得到hash值
   int hash = spread(key.hashCode());
   int binCount = 0;
   for (Node<K,V>[] tab = table;;) {
       Node<K,V> f; int n, i, fh;
       //如果table是空的,就去初始化宏娄,下一個(gè)循環(huán)就不是空的了
       if (tab == null || (n = tab.length) == 0)
           tab = initTable();
           //如果沒有取到值,即取i位的元素是空的逮壁,為什么i取值是(n-1)&hash??
           //這是hash的精華所在孵坚,在這里可以先思考一下
           //此時(shí)直接到KV包裝成Node節(jié)點(diǎn)放在i位置即可
       else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
           if (casTabAt(tab, i, null,
                        new Node<K,V>(hash, key, value, null)))
               break;                   // no lock when adding to empty bin
       }
       //MOVED,定義為-1窥淆。標(biāo)記原table正在執(zhí)行擴(kuò)容任務(wù)卖宠,可以去幫忙(支持多線程擴(kuò)容)
       else if ((fh = f.hash) == MOVED)
           tab = helpTransfer(tab, f);
       else {
           //這種情況是,在i的位置找到了一個(gè)元素忧饭,說明此元素的K與之間的某個(gè)K的hash結(jié)果是一樣的
           //
           V oldVal = null;
           synchronized (f) {//同步鎖住第一個(gè)元素
               if (tabAt(tab, i) == f) {//為了安全起見扛伍,再一次判斷
                   if (fh >= 0) {//節(jié)點(diǎn)的hash值大于0,說明是一個(gè)鏈表結(jié)構(gòu)
                       binCount = 1;//記錄鏈表的元素個(gè)數(shù)
                       for (Node<K,V> e = f;; ++binCount) {
                           K ek;
                           //判斷給定的key是否與取出的key相同词裤,如果是則替換元素
                           if (e.hash == hash &&
                               ((ek = e.key) == key ||
                                (ek != null && key.equals(ek)))) {
                               oldVal = e.val;
                               if (!onlyIfAbsent)
                                   e.val = value;
                               break;//直接跳出刺洒,這是一種思想。在編程時(shí)可以減少一些if else判斷
                           }
                           //否則就是不相等吼砂,那就把此元素放在鏈表的最后一個(gè)元素
                           Node<K,V> pred = e;
                           if ((e = e.next) == null) {
                               pred.next = new Node<K,V>(hash, key,
                                                         value, null);
                               break;
                           }
                       }
                   }
                   //如果不是鏈表逆航,而是紅黑樹
                   else if (f instanceof TreeBin) {
                       Node<K,V> p;
                       binCount = 2;
                       //把元素放入樹中的對(duì)應(yīng)位置 
                       if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                      value)) != null) {
                           oldVal = p.val;
                           if (!onlyIfAbsent)
                               p.val = value;
                       }
                   }
               }
           }
           if (binCount != 0) {
               //鏈表的元素大于等于8時(shí),就把鏈表轉(zhuǎn)換為紅黑樹
               if (binCount >= TREEIFY_THRESHOLD)
                   treeifyBin(tab, i);
               if (oldVal != null)
                   return oldVal;
               break;
           }
       }
   }
   //新添加一個(gè)元素渔肩,size加1因俐,可能會(huì)觸發(fā)擴(kuò)容
   addCount(1L, binCount);
   return null;
}
CAS性能很高,但是我知道synchronized性能可不咋地,為啥jdk1.8升級(jí)之后反而多了synchronized抹剩?

synchronized之前一直都是重量級(jí)的鎖撑帖,但是后來java官方是對(duì)他進(jìn)行過升級(jí)的,他現(xiàn)在采用的是鎖升級(jí)的方式去做的澳眷。
針對(duì) synchronized 獲取鎖的方式胡嘿,JVM 使用了鎖升級(jí)的優(yōu)化方式,就是先使用偏向鎖優(yōu)先同一線程使用,然后再次獲取鎖境蔼,如果失敗灶平,就升級(jí)為 CAS 輕量級(jí)鎖,如果失敗就會(huì)短暫自旋箍土,防止線程被系統(tǒng)掛起逢享。最后如果一直都失敗就升級(jí)為重量級(jí)鎖。
所以是一步步升級(jí)上去的吴藻,最初也是通過很多輕量級(jí)的方式鎖定的瞒爬。

ConcurrentHashMap的get操作又是怎么樣子的呢?
  • 根據(jù)計(jì)算出來的 hashcode 尋址沟堡,如果就在桶上那么直接返回值侧但。
  • 如果是紅黑樹那就按照樹的方式獲取值。
  • 就不滿足那就按照鏈表的方式遍歷獲取值航罗。
  • 另外concurrenthashmap的值以及next指針等都是用volatile修飾的不用擔(dān)心線程之間可見性問題
關(guān)于ConcurrentHashMap主要注意的地方
  • 1.size()方法和mappingCount方法的異同禀横,兩者計(jì)算是否準(zhǔn)確?
    ??1.1 size最大返回 int 最大值粥血,但是這個(gè) Map 的長(zhǎng)度是有可能超過 int 最大值的
    mappingCount 方法的返回值是 long 類型柏锄。所以不必限制最大值必須是Integer.MAX_VALUE
    ??1.2 public long mappingCount()返回映射的數(shù)量。應(yīng)該使用此方法而不是size()复亏,因?yàn)镃oncurrentHashMap可能包含的映射數(shù)多于可以表示為int的映射趾娃。返回的值是估計(jì)值; 如果有并發(fā)插入或刪除,實(shí)際計(jì)數(shù)可能會(huì)有所不同缔御。
    size()返回此映射中鍵 - 值映射的數(shù)量抬闷。

  • 2.CouncurrentHahsMap的并發(fā)擴(kuò)容問題
    CouncurrentHahsMap在添加元素時(shí)候會(huì)判斷是否有線程在擴(kuò)容,如果有,它會(huì)調(diào)用一個(gè)helpTransfer()方法,在這個(gè)方法里會(huì)重新確認(rèn)一下,如果確實(shí)擴(kuò)容,會(huì)添加一個(gè)sizeCtl開啟一個(gè)輔助線程幫助擴(kuò)容.

  • 3我們這里保證了并發(fā)的寫put操作是安全的,那么get操作沒有加鎖,他是怎么保證并發(fā)讀寫線程安全的呢???
    答案:
    get操作可以無鎖是由于Node的元素val和指針next都是用volatile修飾的,在多線程環(huán)境下線程A修改結(jié)點(diǎn)的val或者新增節(jié)點(diǎn)的時(shí)候是對(duì)線程B可見的耕突。

    1. putval里的addCount原理https://blog.csdn.net/u010285974/article/details/106301938/

HashMap笤成、Hashtable、ConccurentHashMap三者區(qū)別

參考
https://blog.csdn.net/xpsallwell/article/details/88071038
https://blog.csdn.net/qq_27037443/article/details/94441885

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末眷茁,一起剝皮案震驚了整個(gè)濱河市疹启,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蔼卡,老刑警劉巖喊崖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挣磨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡荤懂,警方通過查閱死者的電腦和手機(jī)茁裙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來节仿,“玉大人晤锥,你說我怎么就攤上這事±认埽” “怎么了矾瘾?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)箭启。 經(jīng)常有香客問我壕翩,道長(zhǎng),這世上最難降的妖魔是什么傅寡? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任放妈,我火速辦了婚禮,結(jié)果婚禮上荐操,老公的妹妹穿的比我還像新娘芜抒。我一直安慰自己,他們只是感情好托启,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布宅倒。 她就那樣靜靜地躺著,像睡著了一般屯耸。 火紅的嫁衣襯著肌膚如雪唉堪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天肩民,我揣著相機(jī)與錄音,去河邊找鬼链方。 笑死持痰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祟蚀。 我是一名探鬼主播工窍,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼前酿!你這毒婦竟也來了患雏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤罢维,失蹤者是張志新(化名)和其女友劉穎淹仑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匀借,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年颜阐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吓肋。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凳怨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出是鬼,到底是詐尸還是另有隱情肤舞,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布均蜜,位于F島的核電站李剖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏兆龙。R本人自食惡果不足惜杖爽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望紫皇。 院中可真熱鬧慰安,春花似錦、人聲如沸聪铺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铃剔。三九已至撒桨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間键兜,已是汗流浹背凤类。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留普气,地道東北人谜疤。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像现诀,于是被迫代替她去往敵國(guó)和親夷磕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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