ConcurrentHashMap源碼分析(JAVA 8)(綜述)

一、ConcurrentHashMap緣起

  • HashMap存在線程安全

       話說在hashMap出現之際,就獲得了無數java開發者的青睞,但是隨着數據量的增多,迫使去解決多線程併發訪問,首先在java7中由於採用的擴容方式(參考:https://blog.csdn.net/qq_32679835/article/details/90447248#_330)
會產生死鎖的局面,即使在Java8中改進了原來頭插擴容方式,依舊會存在缺少鍵值對的情況,必須去找到一個替代者去解決HashMap線程不安全的處境,HashTable作爲老牌勢力,雖然滿足了線程安全的要求,但是他是通過synchronized關鍵字實現,通過互斥鎖效率問題就值得商榷。

  • Collection.synchronizedMap解決問題

        跟Hashtable可以說是難兄難弟,只是將Map的相應功能添加同步(synchrinized字段實現),性能與吞吐量存在問題,需要去選擇一種新型數據結構來達到線程安全,此時ConcurrentHashMap出現了。

二、ConcurrentHashMap 的升級打怪

                                  —jdk1.7、jdk1.8實現方式存在差異

  • JDK 1.7

1、HashMap還沒有加入紅黑樹的概念,ConcurrentHashMap 選擇通過segment分段鎖來實現併發,每一個segment會負責管理HashMap數組中的幾個桶,這樣每一次對數組的操作就不需要鎖定整個數組,而是只需要獲得當前segment鎖,相當於將鎖的粒度細化。
此時考慮size()這些具有全局功能的函數,你要獲取總共插入的元素個數,必然要獲取所有segment鎖,之後獲取總數,顯得有點麻煩,因此在此之前首先會先試圖使用無鎖的方式統計總數,這個嘗試會進行3次,如果在相鄰的2次計算中獲得的Segment的modCount次數一致,代表這兩次計算過程中都沒有發生過修改操作,那麼就可以當做最終結果返回。
2、ReentrantLock+segment+HashEntry實現

在這裏插入圖片描述

  • jdk 1.8

1、加入了紅黑樹的概念,將原本在鏈表中查找數據的時間複雜度O(n)修改爲O(log n)
2、修改7中擴容的頭插方式,能夠很好的避免了死鎖問題
3、雖然在1.8中依舊保存segment的靜態類,完全摒棄了了segment分段鎖的概念,而是選擇了一種更加細粒度的操作,在每一個數組的node節點通過synchronized來同步
4、synchronized+cas+HashEntry實現

三、ConcurrentHashMap武器解析(底層實現)

(一) 常量

// node數組最大容量:2^30=1073741824
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默認初始值,必須是2的幕數
private static final int DEFAULT_CAPACITY = 16;
//數組可能最大值,需要與toArray()相關方法關聯
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//併發級別,遺留下來的,爲兼容以前的版本
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 負載因子
private static final float LOAD_FACTOR = 0.75f;
// 鏈表轉紅黑樹閥值,> 8 鏈表轉換爲紅黑樹
static final int TREEIFY_THRESHOLD = 8;
//樹轉鏈表閥值,小於等於6(tranfer時,lc、hc=0兩個計數器分別++記錄原bin、新binTreeNode數量,<=UNTREEIFY_THRESHOLD 則untreeify(lo))
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
private static final int MIN_TRANSFER_STRIDE = 16;
private static int RESIZE_STAMP_BITS = 16;
// 2^15-1,help resize的最大線程數
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 32-16=16,sizeCtl中記錄size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
// forwarding nodes的hash值
static final int MOVED     = -1;
// 樹根節點的hash值
static final int TREEBIN   = -2;
// ReservationNode的hash值
static final int RESERVED  = -3;
// 可用處理器數量
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存放node的數組
transient volatile Node<K,V>[] table;
//控制標識符,用來控制table的初始化和擴容的操作,不同的值有不同的含義
 //當爲負數時:-1代表正在初始化,-N代表有N-1個線程正在 進行擴容
 //當爲0時:代表當時的table還沒有被初始化
 //當爲正數時:表示初始化或者下一次進行擴容的大小
private transient volatile int sizeCtl;


(二) 構造函數

  • ConcurrentHashMap()以默認常量初始化
  • ConcurrentHashMap(int initialCapacity)以給定容量初始化,初始化容量爲大於initialCapacity最小二次冪(tableSizeFor()產生)
  • ConcurrentHashMap(Map<? extends K, ? extends V> m)容量爲傳入的Map大小,迭代(entrySet()函數)後通過putVal()傳參
  • ConcurrentHashMap(int initialCapacity, float loadFactor)默認調用底下參數
  • ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)構造size = (long)(1.0 + (long)initialCapacity / loadFactor)大小的Map

(三) 主要的內部靜態類

  • Node類

1、屬性:hash(每一個元素的標識符,在1.7中用來定位segment的位置和數組位置,1.8中用來定位數組位置)、key、value、next
2、
在這裏插入圖片描述
1、需要提到的是find()方法,對map.get()提供虛擬支持,在Node類的子類中需要重寫find()方法
2、不允許直接設置value值,會拋出異常

 Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
  • TreeNode類:構成二叉樹的基本元素,重寫了find()方法
  • TreeBin類:紅黑樹的頭部節點,選擇了cas+讀寫鎖的策略,能夠使得在writer之前需要需要等待reader完成,使用TreeBin代替了TreeNode節點,實際上在table數組中,存放的是TreeBin對象
  • ForwardingNode類:數組擴容時候數據轉移的臨時節點,一個用於連接兩個table的節點類。它包含一個nextTable指針,用於指向下一張表。
  • CounterCell 主要用來計數,首先整體的計數肯定通過cas操作baseCount變量,但是由於多線程操作,肯定會出現自選失敗的情況,如果出現自旋失敗,就需要去存儲到對應的COunterCell數組中,在獲取ConcurrenthashMap總個數的時候,只需要baseCount+CounterCell中的數量

(三) 關鍵方法

  • Native方法:保證了在多線程下的可見性和有序性,遵循內存屏障
//在tab中查找偏移量爲i的Node節點
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i)
//通過cas設置偏移量爲i的值,如果等於期待值就設置變量
static final <K,V> Boolean casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v)
//通過cas設置偏移量爲i的值
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)
  • initTable():初始化數組
    如果自旋設置sizeCtl =-1,表明當前線程正在初始化,初始化完成之後,sizeCtl 等於總容量的3/4,作爲觸發擴容的閾值
private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
                Thread.yield(); //有其他線程在初始化
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//自旋,如果能夠成功,則進入初始化
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//默認容量爲16
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);//容量爲總容量的3/4
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }
  • get()方法:獲取key值對應的value
 public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //計算hash,通過高16位與低16位異或進行擾亂,避免hash衝突的發生
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                //鏈表頭結點
                    return e.val;
            }
            else if (eh < 0)
            //特殊節點,通過find方法查找
            /**
             static final int MOVED     = -1; // hash for forwarding nodes(擴容時候使用)
    		 static final int TREEBIN   = -2; // hash for roots of trees(轉化爲二叉樹使用)
   		     static final int RESERVED  = -3; // hash for transient reservations 
            **/
                return (p = e.find(h, key)) != null ? p.val : null;
             //鏈表查找
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
  • put()方法:與HashMap方法相同,調用putval添加值

     只需要理解每一個函數出現的作用,因爲COncurrentHashMap涉及到擴容,因此在之後擴容部分,將詳細講解helpTransfer()、addCount()
    

整個添加元素的過程
(1)首先獲取元素hash值,如果數組沒有初始化,需要去初始化數組,之後去添加元素
(2)空節點中添加了forward節點(虛擬表示:MOVED),說明有其他線程在擴容需要去協助擴容
(3)如果是插入到鏈表中(fh>0),插入首節點直接插入,插入節點key值相同,則覆蓋,否則遍歷鏈表,插入鏈表結尾
(4)如果是紅黑樹節點,插入到紅黑樹
(5)判斷鏈表節點加入後是否超過閾值8,超過閾值轉化爲紅黑樹
(6)計數,在ConcurrentHashMap中元素個數

 final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //如果爲null或者table數組未初始化
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
                //添加到鏈表頭結點
            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
            }
            //有一個線程在擴容,需要當前線程協助擴容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //鎖定當前節點,添加元素
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                    //表明是非樹節點
                        if (fh >= 0) {
                            binCount = 1;
                            //遍歷鏈表所有節點
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //key值一致,則節點覆蓋
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                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;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                //鏈表節點數目大於8,轉化爲紅黑樹
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
       //將當前ConcurrentHashMap的元素數量+1  
        addCount(1L, binCount);
        return null;
    }
  • 擴容函數
    1、 addCount()計數函數涉及到的擴容部分(爲了理解transfer)
private final void addCount(long x, int check) {
     //CounterCell實現單個線程的計數.....(省略了計數,後續補充)
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            //觸發擴容
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                   //擴容標識符
                int rs = resizeStamp(n);
                //sc<0,其他線程在擴容
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                        //當前已經有其他線程在擴容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //唯一或者第一個擴容的線程
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

首先是resizeStamp(),該函數返回一個用於數據校驗的標誌位,意思是對長度爲n的table進行擴容。它將n的前導零(最高有效位之前的零的數量)和1 << 15做或運算,這時低16位的最高位爲1,其他都爲n的前導零。

static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

rs << RESIZE_STAMP_SHIFT) + 2含義,首先RESIZE_STAMP_SHIFT = 32-RESIZE_STAMP_BITS(默認爲16),rs是擴容的標識符,將該標識符左移16位,剩餘的16位就表示擴容線程的數量,由於-1表示table數組初始化,因此會選擇+2的方式,特別是該操作需要理解,在後邊會使用。

2、transfer()
單線程擴容
它的大體思想就是遍歷、複製的過程。首先根據運算得到需要遍歷的次數i,然後利用tabAt方法獲得i位置的元素:
(1)如果這個位置爲空,就在原table中的i位置放入forwardNode節點,這個也是觸發併發擴容的關鍵點;
(2)如果這個位置是Node節點(fh>=0),根據hash&n 判斷高位,將該鏈表拆分爲原位置鏈表和i+n(原始數組容量)的鏈表,放在對應位置
(3)如果這個位置是TreeBin節點(fh<0),根據hash&n 判斷高位,將該紅黑樹拆分爲原位置紅黑樹和i+n(原始數組容量)的紅黑樹,放在對應位置
(4)遍歷過所有的節點以後就完成了複製工作,這時讓nextTable作爲新的table,並且更新sizeCtl爲新容量的0.75倍,完成擴容。
多線程擴容
如果遍歷到的節點是forward節點,就向後繼續遍歷,再加上給節點上鎖的機制,就完成了多線程的控制。多線程遍歷節點,處理了一個節點,就把對應點的值set爲forward,另一個線程看到forward,就向後遍歷。這樣交叉就完成了複製工作。而且還很好的解決了線程安全的問題。

//一個過渡的table表  只有在擴容的時候纔會使用 
private transient volatile Node<K,V>[] nextTable;  
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        //確定好每一個線程所負責的桶範圍
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; 
        if (nextTab == null) {            // 初始化桶,爲原來容量的2倍
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        //設置forwardingNode節點,用於標誌位
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//在擴容是忽略,有這個標識的bucket標識已經有其他線程處理(將對應桶中的數據放置到新數組中)
        boolean advance = true;//併發擴容的關鍵屬性 如果等於true 說明這個節點已經處理過  
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)//當前線程已經分配了bucket區域,但是依舊存在未處理的桶
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {//所有bucket已經分配完畢
                    i = -1;
                    advance = false;
                }
                //爲當前線程設置負責的bucket範圍
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //所有節點完成複製,則清空nextTable並將新數組賦值給原始數組
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //擴容線程數量-1,有很多線程,高16位標識擴容,低16位標識擴容線程數量
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                //當前還有其他線程正在擴容,直接返回,如果==,就表示當前只有本線程擴容(參考addCout理解)
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //空節點直接設置爲fwd節點
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // 其他線程已經處理當前bucket
            else {
            //鎖定當前節點
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                        //與HashMap擴容原理相同,產生原鏈表和反序鏈表
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            //直接複製到原數組位置
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                            //複製到i+原數組容量的位置
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);//添加到原數組
                            setTabAt(nextTab, i + n, hn);//添加到i+n的位置
                            setTabAt(tab, i, fwd);
                            advance = true;//該bucket已經處理
                        }
                        //樹節點擴容同樣思路,產生原始位置的紅黑樹與i+n位置的紅黑樹
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            //轉換後不滿足閾值6,將二叉樹轉化爲鏈表
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

3、tryPresize()擴容函數應用:再理解一下多線程擴容

 private final void tryPresize(int size) {
 //初始化容量
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
            tableSizeFor(size + (size >>> 1) + 1);
        int sc;
         // sizeCtl是默認值或正整數
    	// 代表table還未初始化
    	// 或還沒有其他線程正在進行擴容
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            if (tab == null || (n = tab.length) == 0) {
                n = (sc > c) ? sc : c;
                //初始化
                if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        if (table == tab) {
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = nt;
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;//容量
                    }
                }
            }
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
              //與addCount異曲同工,開始擴容
            else if (tab == table) {
                int rs = resizeStamp(n);
                if (sc < 0) {
                    Node<K,V>[] nt;
                    //標識位不相等,擴容結束
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                      //增加一個線程擴容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //唯一或者第一個線程擴容
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
            }
        }
    }
  • 計數addCount():補充前邊省略的部分
    (1)更新baseCount值
    (2)檢查是否需要擴容,並擴容(前邊已經說明)
 private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        // cas更新baseCount失敗,每個線程都會通過ThreadLocalRandom.getProbe() & m尋址找到屬於它的CounterCell,然後進行計數。
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                  //調用fullAddCount(),該函數負責初始化CounterCells和更新計數
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
              //統計總數(baseCount+所有CounterCells數組個數)
            s = sumCount();
        }
        //.....擴容省略
}

(3)fullAddCount()初始化CounterCells和更新計數

 private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      //相當於找到每一個線程對應的Hash
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;//未競爭標識
        }
        boolean collide = false;                // 衝突標識,True if last slot nonempty
        for (;;) {
            CounterCell[] as; CounterCell a; int n; long v;+
            if ((as = counterCells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                //0和1,當它被設置爲0時表示沒有加鎖,當它被設置爲1時表示已被其他線程加鎖
                    if (cellsBusy == 0) {            // Try to attach new Cell
                    //通過內部類,添加數量
                        CounterCell r = new CounterCell(x); 
                        //自旋加鎖
                        if (cellsBusy == 0 &&   U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            boolean created = false;
                            //添加計數變量
                            try {               // Recheck under lock
                                CounterCell[] rs; int m, j;
                                if ((rs = counterCells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;//釋放鎖
                            }
                          // 如果未成功
                        // 代表as[(n - 1) & h]這個位置的Cell已經被其他線程設置
                        // 那麼就從循環頭重新開始
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // (a = as[(n - 1) & h]) != null 表示有其他線程競爭
                    wasUncontended = true;      // 需要去重新計算hash
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))//成功則退出循環
                    break;
                else if (counterCells != as || n >= NCPU) // 達到了數組容量最大值,無法擴容
                    collide = false;           
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 &&
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                    try {
                        if (counterCells == as) {//擴容爲原來的2倍,並數據遷移到新數組
                            CounterCell[] rs = new CounterCell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            counterCells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = ThreadLocalRandom.advanceProbe(h);//當前線程重新計算hash
            }
            //表明數組未初始化
            else if (cellsBusy == 0 && counterCells == as &&  U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                boolean init = false;
                try {                           // 初始化counterCells 數組
                    if (counterCells == as) {
                        CounterCell[] rs = new CounterCell[2];//初始化容量爲2
                        rs[h & 1] = new CounterCell(x);
                        counterCells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                 // 初始化CounterCell數組成功,退出循環
                if (init)
                    break;
            }
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                break;                          //失敗則使用baseCount
        }
    }

四、引用

【1】 jdk1.7 segment結構圖:https://sylvanassun.github.io/2018/03/16/2018-03-16-map_family/#more
【2】參考主力:https://blog.csdn.net/yjp198713/article/details/79004798#forwardingnode
【3】參考主力:https://blog.csdn.net/qq_30336433/article/details/82586416#擴容

五、結語

看Concurrenthashmap比較吃力,主要是分析了1.8中的特性,對於1.7中的特性只是提到了一小塊,其中還有很多部分需要日後繼續補充。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章