ConcurrentHashMap詳解

ConcurrentHashMap詳解

JDK1.7版本的CurrentHashMap的實(shí)現(xiàn)原理

在JDK1.7中ConcurrentHashMap采用了數(shù)組+Segment+分段鎖的方式實(shí)現(xiàn)仿贬。

1.Segment(分段鎖)

ConcurrentHashMap中的分段鎖稱(chēng)為Segment,它即類(lèi)似于HashMap的結(jié)構(gòu)睬澡,即內(nèi)部擁有一個(gè)Entry數(shù)組艇拍,數(shù)組中的每個(gè)元素又是一個(gè)鏈表,同時(shí)又是一個(gè)ReentrantLock(Segment繼承了ReentrantLock)。

2.內(nèi)部結(jié)構(gòu)

ConcurrentHashMap使用分段鎖技術(shù)骏掀,將數(shù)據(jù)分成一段一段的存儲(chǔ)鸠澈,然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問(wèn)其中一個(gè)段數(shù)據(jù)的時(shí)候截驮,其他段的數(shù)據(jù)也能被其他線程訪問(wèn)笑陈,能夠?qū)崿F(xiàn)真正的并發(fā)訪問(wèn)。如下圖是ConcurrentHashMap的內(nèi)部結(jié)構(gòu)圖:

分段鎖

從上面的結(jié)構(gòu)我們可以了解到葵袭,ConcurrentHashMap定位一個(gè)元素的過(guò)程需要進(jìn)行兩次Hash操作涵妥。

第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部坡锡。

3.該結(jié)構(gòu)的優(yōu)劣勢(shì)
壞處

這一種結(jié)構(gòu)的帶來(lái)的副作用是Hash的過(guò)程要比普通的HashMap要長(zhǎng)

好處

寫(xiě)操作的時(shí)候可以只對(duì)元素所在的Segment進(jìn)行加鎖即可蓬网,不會(huì)影響到其他的Segment,這樣鹉勒,在最理想的情況下拳缠,ConcurrentHashMap可以最高同時(shí)支持Segment數(shù)量大小的寫(xiě)操作(剛好這些寫(xiě)操作都非常平均地分布在所有的Segment上)。

所以贸弥,通過(guò)這一種結(jié)構(gòu)窟坐,ConcurrentHashMap的并發(fā)能力可以大大的提高。

JDK1.8中的實(shí)現(xiàn)

ConcurrentHashMap取消了segment分段鎖绵疲,而采用CAS和synchronized來(lái)保證并發(fā)安全哲鸳。數(shù)據(jù)結(jié)構(gòu)跟HashMap1.8的結(jié)構(gòu)一樣,數(shù)組+鏈表/紅黑二叉樹(shù)盔憨。 synchronized只鎖定當(dāng)前鏈表或紅黑二叉樹(shù)的首節(jié)點(diǎn)徙菠,這樣只要hash不沖突,就不會(huì)產(chǎn)生并發(fā)郁岩,效率又提升N倍婿奔。

JDK1.8的ConcurrentHashMap的結(jié)構(gòu)圖如下:

ConcurrentHashMap 源碼分析

ConcurrentHashMap 類(lèi)結(jié)構(gòu)參照HashMap缺狠,這里列出HashMap沒(méi)有的幾個(gè)屬性。

/**   
hash表初始化或擴(kuò)容時(shí)的一個(gè)控制位標(biāo)識(shí)量萍摊。     
負(fù)數(shù)代表正在進(jìn)行初始化或擴(kuò)容操作     
-1代表正在初始化     
-N 表示有N-1個(gè)線程正在進(jìn)行擴(kuò)容操作     
正數(shù)或0代表hash表還沒(méi)有被初始化挤茄,這個(gè)數(shù)值表示初始化或下一次進(jìn)行擴(kuò)容的大小     */
    private transient volatile int sizeCtl; 
    // 以下兩個(gè)是用來(lái)控制擴(kuò)容的時(shí)候 單線程進(jìn)入的變量
    private static int RESIZE_STAMP_BITS = 16;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    static final int MOVED     = -1; // hash值是-1,表示這是一個(gè)forwardNode節(jié)點(diǎn)
    static final int TREEBIN   = -2; // hash值是-2  表示這時(shí)一個(gè)TreeBin節(jié)點(diǎn)

分析代碼主要目的:分析是如果利用CAS和Synchronized進(jìn)行高效的同步更新數(shù)據(jù)冰木。
下面插入數(shù)據(jù)源碼:

public V put(K key, V value) {
    return putVal(key, value, false);
}

    /** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    //ConcurrentHashMap 不允許插入null鍵穷劈,HashMap允許插入一個(gè)null鍵
    if (key == null || value == null) throw new NullPointerException();
    //計(jì)算key的hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    //for循環(huán)的作用:因?yàn)楦略厥鞘褂肅AS機(jī)制更新,需要不斷的失敗重試踊沸,直到成功為止歇终。
    for (Node<K,V>[] tab = table;;) {
        // f:鏈表或紅黑二叉樹(shù)頭結(jié)點(diǎn),向鏈表中添加元素時(shí)逼龟,需要synchronized獲取f的鎖评凝。
        Node<K,V> f; int n, i, fh;
        //判斷Node[]數(shù)組是否初始化,沒(méi)有則進(jìn)行初始化操作
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //通過(guò)hash定位Node[]數(shù)組的索引坐標(biāo)腺律,是否有Node節(jié)點(diǎn)肥哎,如果沒(méi)有則使用CAS進(jìn)行添加(鏈表的頭結(jié)點(diǎn)),添加失敗則進(jìn)入下次循環(huán)疾渣。
        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
        }
        //檢查到內(nèi)部正在移動(dòng)元素(Node[] 數(shù)組擴(kuò)容)
        else if ((fh = f.hash) == MOVED)
            //幫助它擴(kuò)容
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //鎖住鏈表或紅黑二叉樹(shù)的頭結(jié)點(diǎn)
            synchronized (f) {
                //判斷f是否是鏈表的頭結(jié)點(diǎn)
                if (tabAt(tab, i) == f) {
                    //如果fh>=0 是鏈表節(jié)點(diǎn)
                    if (fh >= 0) {
                        binCount = 1;
                        //遍歷鏈表所有節(jié)點(diǎn)
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //如果節(jié)點(diǎn)存在,則更新value
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            //不存在則在鏈表尾部添加新節(jié)點(diǎn)崖飘。
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    //TreeBin是紅黑二叉樹(shù)節(jié)點(diǎn)
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        //添加樹(shù)節(jié)點(diǎn)
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                      value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }

            if (binCount != 0) {
                //如果鏈表長(zhǎng)度已經(jīng)達(dá)到臨界值8 就需要把鏈表轉(zhuǎn)換為樹(shù)結(jié)構(gòu)
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //將當(dāng)前ConcurrentHashMap的size數(shù)量+1
    addCount(1L, binCount);
    return null;
}

  1. 判斷Node[]數(shù)組是否初始化榴捡,沒(méi)有則進(jìn)行初始化操作
  2. 通過(guò)hash定位Node[]數(shù)組的索引坐標(biāo),是否有Node節(jié)點(diǎn)朱浴,如果沒(méi)有則使用CAS進(jìn)行添加(鏈表的頭結(jié)點(diǎn))吊圾,添加失敗則進(jìn)入下次循環(huán)。
  3. 檢查到內(nèi)部正在擴(kuò)容翰蠢,如果正在擴(kuò)容项乒,就幫助它一塊擴(kuò)容。
  4. 如果f!=null梁沧,則使用synchronized鎖住f元素(鏈表/紅黑二叉樹(shù)的頭元素)
    4.1 如果是Node(鏈表結(jié)構(gòu))則執(zhí)行鏈表的添加操作檀何。
    4.2 如果是TreeNode(樹(shù)型結(jié)果)則執(zhí)行樹(shù)添加操作。
  5. 判斷鏈表長(zhǎng)度已經(jīng)達(dá)到臨界值8 就需要把鏈表轉(zhuǎn)換為樹(shù)結(jié)構(gòu)廷支。

總結(jié):
????JDK8中的實(shí)現(xiàn)也是鎖分離的思想频鉴,它把鎖分的比segment(JDK1.5)更細(xì)一些,只要hash不沖突恋拍,就不會(huì)出現(xiàn)并發(fā)獲得鎖的情況垛孔。它首先使用無(wú)鎖操作CAS插入頭結(jié)點(diǎn),如果插入失敗施敢,說(shuō)明已經(jīng)有別的線程插入頭結(jié)點(diǎn)了周荐,再次循環(huán)進(jìn)行操作狭莱。如果頭結(jié)點(diǎn)已經(jīng)存在,則通過(guò)synchronized獲得頭結(jié)點(diǎn)鎖概作,進(jìn)行后續(xù)的操作腋妙。性能比segment分段鎖又再次提升。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仆嗦,一起剝皮案震驚了整個(gè)濱河市辉阶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘩扼,老刑警劉巖谆甜,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異集绰,居然都是意外死亡规辱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)栽燕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)罕袋,“玉大人,你說(shuō)我怎么就攤上這事碍岔≡⊙叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵蔼啦,是天一觀的道長(zhǎng)榆纽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捏肢,這世上最難降的妖魔是什么奈籽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鸵赫,結(jié)果婚禮上衣屏,老公的妹妹穿的比我還像新娘。我一直安慰自己辩棒,他們只是感情好狼忱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著一睁,像睡著了一般藕赞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卖局,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天斧蜕,我揣著相機(jī)與錄音,去河邊找鬼砚偶。 笑死批销,一個(gè)胖子當(dāng)著我的面吹牛洒闸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播均芽,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丘逸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了掀宋?” 一聲冷哼從身側(cè)響起深纲,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎劲妙,沒(méi)想到半個(gè)月后湃鹊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镣奋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年币呵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侨颈。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡余赢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哈垢,到底是詐尸還是另有隱情妻柒,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布耘分,位于F島的核電站举塔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏陶贼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一待秃、第九天 我趴在偏房一處隱蔽的房頂上張望拜秧。 院中可真熱鬧,春花似錦章郁、人聲如沸枉氮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)聊替。三九已至,卻和暖如春培廓,著一層夾襖步出監(jiān)牢的瞬間惹悄,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工肩钠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泣港,地道東北人暂殖。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像当纱,于是被迫代替她去往敵國(guó)和親呛每。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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