ConcurrentHashMap的了解(基于JDK 1.8)

ConcurrentHashMap也是一種Map,也可以用來存放key-value,但它是線程安全的Map,在高并發(fā)的情況下,不會像HashMap那樣出現(xiàn)添加數(shù)據(jù)丟失,擴展形成死鏈的情況。HashTable也是線程安全的,但是相比HashTable直接加鎖的做法,1.8里面的ConcurrentHashMap采用lock-free顯得更加高效。

Map體系圖

由圖可知,ConcurrentHashMap和HashMap一樣,也是繼承AbstractMap,而AbstractMap實現(xiàn)了Map接口。

ConcurrentHashMap源碼大概了解(1.8)
ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu)
    /**
     * concurrentHashMap存放單個key-value的地方,有TreeBin,TreeNode,F(xiàn)orwardingNode,ReservationNode四個子類
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        /**
          * 計算key的得到的哈希值
          * int hash = spread(key.hashCode());
          * static final int spread(int h) {
          *    return (h ^ (h >>> 16)) & HASH_BITS;
          * }
          * static final int HASH_BITS = 0x7fffffff;
          */
        final int hash;
        final K key;
        volatile V val;
        //節(jié)點所在的連接中指向的下一個元素
        volatile Node<K,V> next;
    }

    /**
     * 跟HashMap一樣,table是存放所有key-value的地方,默認為null,當(dāng)插入元素時會初始化,初始化大小為16,每次擴容為原來的兩倍
     */
    transient volatile Node<K,V>[] table;

    /**
     * 擴容時新生成的數(shù)組,大小為table的兩倍
     */
    private transient volatile Node<K,V>[] nextTable;

    /**
     * 重要屬性,用來控制table的初始化和擴容,
     * 0: 默認值,表示初始化table或擴容用默認值16和2倍
     * > 0 : 使用sizeCtl的大小來擴容
     * -1 : 表示正在初始化
     * -n : 表示有(n - 1)個線程正在擴容
     */
    private transient volatile int sizeCtl;

    /**
     * Node鏈表轉(zhuǎn)成紅黑樹會先判斷當(dāng)前整個map的size是否大于MIN_TREEIFY_CAPACITY并且當(dāng)前鏈表大小大于TREEIFY_THRESHOLD
     */
    static final int TREEIFY_THRESHOLD = 8;
    static final int MIN_TREEIFY_CAPACITY = 64;
ConcurrentHashMap添加元素


put()源碼

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //key 和 value 為null都報空指針異常
        if (key == null || value == null) throw new NullPointerException();
        //計算hash值
        int hash = spread(key.hashCode());
        //元素的數(shù)量
        int binCount = 0;
        //一個死循環(huán)
        for (ConcurrentHashMap.Node<K,V>[] tab = table;;) {
            ConcurrentHashMap.Node<K,V> f; int n, i, fh;
            //如果table為null,說明是第一次插數(shù)據(jù),要初始化table
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //(n - 1) & hash)為元素在table中的位置,tabAt是用來獲取tab的第i個元素是否為空
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //用CAS把節(jié)點置上去,如果節(jié)點不為null返回false
                if (casTabAt(tab, i, null,
                        new ConcurrentHashMap.Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            //moved為 -1 ,如果一個節(jié)點的hash值為 -1,表示他是擴容轉(zhuǎn)發(fā)節(jié)點
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            //當(dāng)table不為空,并且tab上第i個元素也不為空的時候執(zhí)行這里,說明要形成鏈表了
            else {
                V oldVal = null;
                //加鎖,為什么要加鎖?
                synchronized (f) {
                    //為什么還要再次比較呢?
                    if (tabAt(tab, i) == f) {
                        //fh是首節(jié)點的哈希值,大于0說明不是紅黑樹,而是鏈表
                        if (fh >= 0) {
                            //鏈表的元素數(shù)量
                            binCount = 1;
                            //遍歷鏈表并且插入
                            for (ConcurrentHashMap.Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //如果是已經(jīng)有的元素,替換它的value
                                if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                                (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                ConcurrentHashMap.Node<K,V> pred = e;
                                //如果已經(jīng)是最后一個元素,把新進來的元素添加進去,否則遍歷鏈表
                                if ((e = e.next) == null) {
                                    pred.next = new ConcurrentHashMap.Node<K,V>(hash, key,
                                            value, null);
                                    break;
                                }
                            }
                        }
                        //紅黑樹做紅黑樹的處理
                        else if (f instanceof ConcurrentHashMap.TreeBin) {
                            ConcurrentHashMap.Node<K,V> p;
                            binCount = 2;
                            if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key,
                                    value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                //如果鏈表元素大于TREEIFY_THRESHOLD 轉(zhuǎn)成紅黑樹
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    //返回元素
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //擴容
        addCount(1L, binCount);
        return null;
    }

initTable()源碼

   private final ConcurrentHashMap.Node<K,V>[] initTable() {

        ConcurrentHashMap.Node<K,V>[] tab; int sc;
        //只有當(dāng)table不為空的時候,才跳出循環(huán)
        while ((tab = table) == null || tab.length == 0) {
            //sizeCtl< 0 說明有其它線程正在初始化table,線程讓出CPU
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            //SIZECTL對應(yīng)的偏移量正式sizeCtl,CAS將sc置為 -1,表示正在初始化,如果返回false,說明傳進去的sc和sizeCtl不一致
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    //再次判斷table是否為空
                    if ((tab = table) == null || tab.length == 0) {
                        //擴容大小,如果sc>0,則用sizeCtl的值,否則用默認值16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        //初始化table數(shù)據(jù)
                        @SuppressWarnings("unchecked")
                        ConcurrentHashMap.Node<K,V>[] nt = (ConcurrentHashMap.Node<K,V>[])new ConcurrentHashMap.Node<?,?>[n];
                        table = tab = nt;
                        //這個地方看不懂
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

get()源碼

  public V get(Object key) {
        ConcurrentHashMap.Node<K,V>[] tab; ConcurrentHashMap.Node<K,V> e, p; int n, eh; K ek;
        //計算哈希值
        int h = spread(key.hashCode());
        //如果table不為空,并且長度大于0,還有對應(yīng)鏈表首節(jié)點不為空
        if ((tab = table) != null && (n = tab.length) > 0 &&
                (e = tabAt(tab, (n - 1) & h)) != null) {
            //如果鏈表首節(jié)點和key的hash值一樣,并且key也一致
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            //這個好像是擴容時候的轉(zhuǎn)發(fā)節(jié)點
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            //遍歷鏈表直到找到key也一樣的節(jié)點
            while ((e = e.next) != null) {
                if (e.hash == h &&
                        ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
ConcurrentHashMap(1.7)也了解一下

1.7中的ConcurrentHashMap采用了分段鎖的機制,底層是一個Segment數(shù)組,而每個Segment可以理解為指向一個HashMap。當(dāng)ConcurrentHashMap進行讀寫操作的時候,首先會計算對應(yīng)Segment數(shù)組的哪個位置,然后計算在segment指向的HashMap的哪個位置。當(dāng)進行寫操作時,會對對應(yīng)的segment進行加鎖,不同的segment并發(fā)操作時不會互相影響。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是Map 不同于List單列的線性結(jié)構(gòu),Map提供的是一種雙列映射的存儲集合,它能夠提供一對一的數(shù)據(jù)處理...
    still_loving閱讀 3,393評論 0 30
  • 沉下心, 聽著你們的合奏。 那樣震撼, 又那樣清脆。 你們是樂師, 也是魔法師。 在自然的舞臺 奏著夏的尾章。 聆...
    葉湫楓閱讀 233評論 0 0
  • 昨晚七點多就準(zhǔn)備睡覺,想著今天要停水停電睡到十來點洗澡洗碗。抱著手機翻到九點多準(zhǔn)備睡覺,各種藥也吃了。準(zhǔn)備等待入睡...
    筒葉花月閱讀 432評論 0 1
  • 想到過無數(shù)次要怎么開始自己的第一篇公眾號的文章。 萬萬沒想到的是這樣一個開始。 總以為可以等到某個具有紀(jì)念意義的歷...
    瀅瀅之水閱讀 182評論 0 0

友情鏈接更多精彩內(nèi)容