【廣撒網(wǎng)】ConcurrentHashMap提供的computeIfAbsent源碼分析

JDK7的ConcurrentHashMap使用Segment分段鎖機(jī)制猪瞬,但是在JDK8采用CAS自旋》+synchronized鎖機(jī)制。并且加鎖的粒度更小话浇,直接鎖Node數(shù)組的第一個(gè)元素增蹭,即實(shí)現(xiàn)了對(duì)單個(gè)key加鎖杆逗。

    private void analysisPo2Cache(Class < ?>clazz) {
       //維護(hù)緩存
        cache.computeIfAbsent(clazz.getName(), key - >{
        HashSet < Field > values = new HashSet < >();
        Field[] declaredFields = clazz.getDeclaredFields();
        Field.setAccessible(declaredFields, true);
        for (Field field: declaredFields) {
            Class < ?>fieldClass = field.getType();
            //判斷是否含有List的屬性
            if (List.class.isAssignableFrom(fieldClass)) {
                values.add(field);
            }
        }
        return values;
    });
}

源碼分析:

JDK8的Map的數(shù)據(jù)結(jié)構(gòu)是Node數(shù)組和鏈表+紅黑樹学少。
線程安全的核心思想:CAS+ synchronized鎖對(duì)Node數(shù)組上第一個(gè)節(jié)點(diǎn)加鎖剪个。

  1. 多個(gè)線程均會(huì)創(chuàng)建ReservationNode節(jié)點(diǎn),均執(zhí)行執(zhí)行synchronized(r)(因?yàn)殒i的是對(duì)象版确,所以所以線程都會(huì)執(zhí)行casTabAt方法)扣囊。CAS失敗的線程釋放鎖,但是CAS成功的線程將執(zhí)行l(wèi)amda表達(dá)式(此時(shí)阀坏,持有synchronized(r)的鎖)如暖。
  2. 失敗的線程將再次自旋笆檀,在(f = tabAt(tab, i = (n - 1) & h)) == null)方法中獲取key所在位置的Node對(duì)象(lamda表達(dá)式?jīng)]執(zhí)行完畢忌堂,那么Node對(duì)象就是r,但是r已經(jīng)被synchronized)酗洒,此時(shí)synchronized(f)會(huì)被阻塞士修,等待synchronized(r)釋放鎖枷遂。當(dāng)?shù)鹊?code>synchronized(r)釋放鎖后,key所在位置的Node對(duì)象會(huì)發(fā)生變化棋嘲,會(huì)再次通過tabAt(tab, i) == f判斷酒唉。開啟下次自旋。
public V computeIfAbsent(K key, Function < ?super K, ?extends V > mappingFunction) {
    //key的lamda表達(dá)式都不能為空
    if (key == null || mappingFunction == null) throw new NullPointerException();
     //右移16位計(jì)算有效的hash值
    int h = spread(key.hashCode());
    V val = null;
    int binCount = 0;
    //開啟自旋
    for (Node <K, V> [] tab = table;;) {
        Node <K,V> f;
        int n,i,fh;
        //初次put時(shí)沸移,因?yàn)閏oncurrenthashmap在構(gòu)造時(shí)沒有初始化table痪伦,所以先初始化table
        if (tab == null || (n = tab.length) == 0) tab = initTable();
        //n為數(shù)組長(zhǎng)度,低位有效二次hash后計(jì)算出數(shù)組的下標(biāo)雹锣,并獲取該下標(biāo)的node對(duì)象(線程不安全)网沾,且node所在的位置為null
        else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
            //創(chuàng)建空節(jié)點(diǎn)(預(yù)定節(jié)點(diǎn))
            Node <K,V> r = new ReservationNode <K,V> ();
            //【神操作】這里加synchronized是為了CAS失敗的線程在此自旋的時(shí)候在synchronized(f)處阻塞,等待CAS維護(hù)value釋放鎖蕊爵。
            synchronized(r) {
                //CAS辉哥,使用預(yù)定節(jié)點(diǎn)去替換null(只有一個(gè)線程成功)
                if (casTabAt(tab, i, null, r)) {
                    //成功進(jìn)入的線程維護(hù)value。
                    binCount = 1;
                    Node <K,V> node = null;
                    try {
                        //執(zhí)行l(wèi)amda表達(dá)式
                        if ((val = mappingFunction.apply(key)) != null) 
                            node = new Node <K,V> (h, key, val, null);
                    } finally {
                        //設(shè)置內(nèi)存的值
                        setTabAt(tab, i, node);
                    }
                }
            }
            //CAS進(jìn)入的線程結(jié)束自旋攒射,其他線程繼續(xù)向下執(zhí)行醋旦。
            if (binCount != 0) break;
        } else if ((fh = f.hash) == MOVED) 
              //node的hash值為-1,表示正在擴(kuò)容会放,本次操作會(huì)幫助擴(kuò)容【多線程完成擴(kuò)容】
              tab = helpTransfer(tab, f);
        else {
            //CAS加鎖失敗后的線程饲齐,自旋最終會(huì)到達(dá)此處
            boolean added = false;
            //【神操作2】即處理了lamda表達(dá)式執(zhí)行慢,sync(r)方法持有鎖咧最,也解決了并發(fā)插入線程安全箩张。
            synchronized(f) {
                //【神操作3】sync(r)釋放鎖后,線程才會(huì)獲取到到此處的鎖窗市,此時(shí)f一定不是tabAt(tab, i) 先慷。需要重新自旋。
                if (tabAt(tab, i) == f) {
                    //fh為該node的hash值咨察,如果hash值為正數(shù)论熙,說明該下標(biāo)是按鏈表存儲(chǔ)的
                    if (fh >= 0) {
                        binCount = 1;
                        //遍歷鏈表(哈希桶)
                        for (Node < K, V > e = f;; ++binCount) {
                            K ek;V ev;
                            //若key相同,直接返回摄狱,結(jié)束自旋脓诡。
                            if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                val = e.val;
                                break;
                            }
                            Node < K,V > pred = e;
                           //如果鏈表中不存在這個(gè)key,則添加到鏈表末尾
                            if ((e = e.next) == null) {
                                if ((val = mappingFunction.apply(key)) != null) {
                                    added = true;
                                    pred.next = new Node < K,V > (h, key, val, null);
                                }
                                break;
                            }
                        }
                    } else if (f instanceof TreeBin) {
                        //紅黑樹的情況
                        binCount = 2;
                        TreeBin < K,V > t = (TreeBin < K, V > ) f;
                        TreeNode < K,V > r,
                        p;
                        //節(jié)點(diǎn)加到紅黑樹
                        if ((r = t.root) != null && (p = r.findTreeNode(h, key, null)) != null) val = p.val;
                        else if ((val = mappingFunction.apply(key)) != null) {
                            added = true;
                            t.putTreeVal(h, key, val);
                        }
                    }
                }
            }
            //加鎖成功的線程binCount會(huì)變?yōu)?
            if (binCount != 0) {
                //bitCount>8媒役,嘗試將鏈表轉(zhuǎn)換為紅黑樹
                if (binCount >= TREEIFY_THRESHOLD) 
                    treeifyBin(tab, i);
                if (!added)
                   return val;
                //結(jié)束自旋
                break;
            }
        }
    }
   //總node+1祝谚,該方法中滿足條件會(huì)進(jìn)行擴(kuò)容。
    if (val != null) addCount(1L, binCount);
    return val;
}

總結(jié):
【廣撒網(wǎng)】若key對(duì)應(yīng)的第一個(gè)Node節(jié)點(diǎn)為null酣衷,每個(gè)線程均會(huì)對(duì)新建的空節(jié)點(diǎn)對(duì)象加鎖交惯,但只有CAS成功的線程一直持有對(duì)象鎖,其他線程釋放鎖,開啟下次自旋席爽。
【實(shí)時(shí)更新】若key對(duì)應(yīng)的第一個(gè)Node節(jié)點(diǎn)不為null意荤,線程獲取最新的Node節(jié)點(diǎn)對(duì)象(但Node節(jié)點(diǎn)可能已經(jīng)被加鎖),嘗試對(duì)Node加鎖去put數(shù)據(jù)只锻。

推薦閱讀

Java8之ConcurrentHashMap實(shí)現(xiàn)原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玖像,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子齐饮,更是在濱河造成了極大的恐慌捐寥,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祖驱,死亡現(xiàn)場(chǎng)離奇詭異上真,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)羹膳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門睡互,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陵像,你說我怎么就攤上這事就珠。” “怎么了醒颖?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵妻怎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我泞歉,道長(zhǎng)逼侦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任腰耙,我火速辦了婚禮榛丢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挺庞。我一直安慰自己晰赞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布选侨。 她就那樣靜靜地躺著掖鱼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪援制。 梳的紋絲不亂的頭發(fā)上戏挡,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音晨仑,去河邊找鬼褐墅。 笑死拆檬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掌栅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼码泛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼猾封!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起噪珊,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤晌缘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后痢站,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磷箕,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年阵难,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了岳枷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呜叫,死狀恐怖空繁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朱庆,我是刑警寧澤盛泡,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站娱颊,受9級(jí)特大地震影響傲诵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜箱硕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一拴竹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剧罩,春花似錦殖熟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至舰罚,卻和暖如春纽门,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背营罢。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工赏陵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饼齿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓蝙搔,卻偏偏與公主長(zhǎng)得像缕溉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吃型,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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