jdk源碼解析三之ConcurrentHashMap

ConcurrentHashMap

在這裏插入圖片描述

 //使用了unSafe方法,通過直接操作內存的方式來保證併發處理的安全性,使用的是硬件的安全機制。
    /*
     * 用來返回節點數組的指定位置的節點的原子操作
     */
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

    /*
     * cas原子操作,在指定位置設定值
     */
    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }
    /*
     * 原子操作,在指定位置設定值
     */
    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

put

    public V put(K key, V value) {

        /*
         * onlyIfAbsent
         *  false:這個value一定會設置
         *  true:只有當這個key的value爲空的時候纔會設置
         */
        return putVal(key, value, false);
    }

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //不允許key/value爲null,否則及時失敗
        if (key == null || value == null) throw new NullPointerException();
        //獲取key的hashCode
        int hash = spread(key.hashCode());
        //用來計算在這個節點總共有多少個元素,用來控制擴容或者轉移爲樹
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //初始化tab
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if (
                //通過哈希計算出一個表中的位置因爲n是數組的長度,所以(n-1)&hash肯定不會出現數組越界
                    (f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //如果這個位置沒有元素的話,則通過cas的方式嘗試添加,注意這個時候是沒有加鎖的
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            /*
             * 如果檢測到某個節點的hash值是MOVED,則表示正在進行數組擴張的數據複製階段,
             * 則當前線程也會參與去複製,通過允許多線程複製的功能,一次來減少數組的複製所帶來的性能損失
             */
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    //再次取出要存儲的位置的元素,跟前面取出來的比較
                    if (tabAt(tab, i) == f) {
                        //取出來的元素的hash值大於0,當轉換爲樹之後,hash值爲-2
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //查找到值,則覆蓋
                                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;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    //鏈表節點其中個數達到8,則擴張數組或轉成樹
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //計數
        addCount(1L, binCount);
        return null;
    }

初始化

   private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            //sizeCtl初始值爲0,當小於0的時候表示在別的線程在初始化表或擴展表
            //則暫停當前正在執行的線程對象,並執行其他線程。
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            else if (
                  /*
                   SIZECTL:當前內存偏移量,
                    sc:期望值
                    -1:表示要替換的值
                */
                    U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        //指定了大小的時候就創建指定大小的Node數組,否則創建指定大小(16)的Node數組
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //初始化後,sizeCtl長度爲數組長度的3/4
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

擴容

    /**
     * Replaces all linked nodes in bin at given index unless table is
     * too small, in which case resizes instead.
     * 數組長度<64,則擴容一倍
     * 否則轉成樹
     */
    private final void treeifyBin(Node<K,V>[] tab, int index) {
        Node<K,V> b; int n, sc;
        if (tab != null) {
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                //擴容
                tryPresize(n << 1);
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
                synchronized (b) {
                    if (tabAt(tab, index) == b) {
                        TreeNode<K,V> hd = null, tl = null;
                        for (Node<K,V> e = b; e != null; e = e.next) {
                            TreeNode<K,V> p =
                                new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                            //把Node組成的鏈表,轉化爲TreeNode的鏈表,頭結點依然放在相同的位置
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        //把TreeNode的鏈表放入容器TreeBin中,內部將單節點樹轉換成紅黑樹
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

 private final void tryPresize(int size) {
        //擴容大小>=最大的一半,直接設置成最大容量
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
                //返回大於輸入參數且最近的2的整數次冪的數
            tableSizeFor(size + (size >>> 1) + 1);
        int sc;
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            //如果數組還沒有初始化
            //putAll的時候,會執行這兒
            if (tab == null || (n = tab.length) == 0) {
                n = (sc > c) ? sc : c;
                //SIZECTL設置-1,表示正在初始化
                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=3/4*n
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                }
            }
            //擴容後的大小<=sizeCtl或者當前數組長度>容量上限,則退出
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            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;
                    //transfer線程數+1,當前線程將加入對transfer的處理
                    //transfer的時候,sc表示在transfer工作的線程數
                    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);
            }
        }
    }


    private static final int tableSizeFor(int c) {

        /*
          讓cap-1再賦值給n的目的是另找到的目標值大於或等於原值。例如二進制1000,十進制數值爲8。
        如果不對它減1而直接操作,將得到答案10000,即16。顯然不是結果。
        減1後二進制爲111,再進行操作則會得到原來的數值1000,即8。
         */
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        //MIN_TRANSFER_STRIDE=16.控制線程數
        //每個CPU最少處理16個長度的數組元素,可以看出核數越高步長越小,也就是說,如果一個數組的長度只有16,那只有一個線程會對其進行擴容的複製移動操作
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        //只有第一個線程進此方法的時候,纔會初始化數組.
        if (nextTab == null) {            // initiating
            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;
        /*
         * 創建一個fwd節點,這個是用來控制併發的,當一個節點爲空或已經被轉移之後,就設置爲fwd節點
         * 這是一個空的標誌節點
         */
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        //是否繼續向前查找的標誌位
        boolean advance = 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;
                //i每次自減,小於範圍  或者 當前線程執行完成  則標記不需要再向前查找
                if (--i >= bound || finishing)
                    advance = false;
                //說明每個桶位置都有線程進行處理,則跳出循環
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //每次修改transferIndex=transferIndex-stride,每個線程處理stride步長的桶
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    //假設當前數組長度爲32,stride=16
                    //則nextIndex=32
                    //transferIndex=nextBound= nextIndex - stride=16
                    //bound=16
                    bound = nextBound;
                    //i=31
                    i = nextIndex - 1;
                    //設置不用向前推進
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //已經完成轉移
                if (finishing) {
                    nextTable = null;
                    //這裏完成nextTab=>table轉換
                    table = nextTab;
                    //爲擴容後的0.75
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //正在工作的線程數-1,並返回
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    //說明執行到了最後一個線程,則直接返回
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                //數組中把null的元素設置爲ForwardingNode節點(hash值爲MOVED)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                //表示已有線程正在處理
                advance = true; // already processed
            else {
                synchronized (f) {
                    //雙重檢查加鎖
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        //>=0說明是node節點
                        if (fh >= 0) {
                            //爲0則表示放在擴容後數組當前索引下,否則放在n+之前位置索引下
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            /*
                            循環結束之後,runBit就是最後不變的hash&n的值
                            也就是說由lastRun節點後的hash&n的值一樣,這樣就可以直接保存,而不需要處理後面的節點
                             */
                            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 {
                                //說明之後的節點都是高位
                                hn = lastRun;
                                ln = null;
                            }
                            //前面的節點不確定高低位,所以遍歷f~lastRun範圍的所有節點
                            //分別逆序存入ln或hn鏈表中
                            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);
                            //設置fwd,這樣其他線程執行的時候,會跳過去.
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        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;
                            //低位鏈表存儲i處
                            setTabAt(nextTab, i, ln);
                            //高位存儲i+n處
                            setTabAt(nextTab, i + n, hn);
                            //原來tab中存儲fwd,標識該桶擴容完成
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }


   final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                //如果 sc 的高 16 位也就是當前n擴容標識,不等於標識符,這說明擴容的容量變化了,不是當前原容量擴容
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs
                        // 如果 sc == 標識符 + 1
                        //      (擴容結束了,不再有線程進行擴容)(默認第一個線程設置 sc ==rs 左移 16 位 + 2,
                        //       當第一個線程結束擴容了,就會將 sc 減一。這個時候,sc 就等於 rs + 1)
                        || sc == rs + 1 ||
                        // 如果 sc == 標識符 + 65535(幫助線程數已經達到最大)
                    sc == rs + MAX_RESIZERS || 
                        //說明已經擴容完成又或者有足夠的線程擴容
                        transferIndex <= 0)
                    break;
                //線程數+1,幫助一起轉換
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;7
                }
            }
            return nextTab;
        }
        return table;
    }

get

    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //如果first匹配key-value,則直接返回
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                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;
    }

addCount

 private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        /*
        as不爲空,說明counterCells數組已創建了,進入條件體繼續執行
        若爲空,則說明數組還沒創建,預測競爭線程少,直接cas操作baseCount
        如果成功,則執行下一步,若失敗,則進入條件體繼續執行
         */
        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 ||
                    //給當前線程隨機生成一個數,獲取counterCells對應值,若爲null,則進入條件體
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                    //條件語句執行到這裏,說明counterCells不爲空,且有值.則嘗試修改個數
                    //修改失敗,則標記競爭
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                //繼續累加次數
                fullAddCount(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            //計算CounterCell總個數
            s = sumCount();
        }
        //擴容
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            //元素個數達到擴容的閥值,且table不爲空,且數組長度小於最大容量
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                //根據length獲取一個標識符
                //高16位置0
                //第16位爲1
                //低15位存當前n擴容標識
                int rs = resizeStamp(n);
                //sc<0說明正在擴容
                if (sc < 0) {
                    // 如果 sc 的高 16 位也就是當前n擴容標識,不等於標識符,這說明擴容的容量變化了,不是當前原容量擴容
                    // 如果 sc == 標識符 + 1
                    //      (擴容結束了,不再有線程進行擴容)(默認第一個線程設置 sc ==rs 左移 16 位 + 2,
                    //       當第一個線程結束擴容了,就會將 sc 減一。這個時候,sc 就等於 rs + 1)
                    // 如果 sc == 標識符 + 65535(幫助線程數已經達到最大)
                    // 如果 nextTable == null(結束擴容了)
                    // 如果 transferIndex <= 0 (轉移狀態變化了)
                    // 結束循環
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    //sc+1,幫助擴容
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //初始化擴容,
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             //高第16位爲1,顯示負數
                                             //高15位容量n擴容標誌
                                            //低16位,並行擴容線程數+1
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    //擴容,第二個參數代表新表,傳入null,表示第一次初始化新表
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

 private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        //如果當前線程隨機數爲0,則強制初始一個值
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      // force initialization
            h = ThreadLocalRandom.getProbe();
            //設置無競爭
            wasUncontended = true;
        }
        //標記是否發生碰撞
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            CounterCell[] as; CounterCell a; int n; long v;
            //counterCells不爲null
            if ((as = counterCells) != null && (n = as.length) > 0) {
                //當前線程所在格子爲空
                if ((a = as[(n - 1) & h]) == null) {
                    //鎖未佔用
                    if (cellsBusy == 0) {            // Try to attach new Cell
                        //新建CounterCell
                        CounterCell r = new CounterCell(x); // Optimistic create
                        //再次檢查鎖未佔用,嘗試加鎖
                        if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            //表示創建CounterCell是否成功狀態
                            boolean created = false;
                            try {               // Recheck under lock
                                CounterCell[] rs; int m, j;
                                //再次檢查counterCells不爲空,且格子爲佔用
                                if ((rs = counterCells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    //設置格子
                                    rs[j] = r;
                                    //設置創建狀態
                                    created = true;
                                }
                            } finally {
                                //釋放鎖
                                cellsBusy = 0;
                            }
                            //正確創建則退出循環
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    //表明cellsBusy=1鎖上發生競爭,則重新生成隨機數,進行下次循環
                    collide = false;
                }
                //CAS 失敗,重新繼續
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //執行到這裏說明wasUncontended=true,認爲無競爭
                //且所在槽有值

                //2:嘗試直接累加
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;
                //3:數組發生變化,說明發生了擴容或者數組長度>=cpu核心數,
                //則認爲無碰撞
                //當擴容超過限制後,則會不停的執行3和4,直到2成功
                else if (counterCells != as || n >= NCPU)
                    collide = false;            // At max size or stale
                //4:數組沒變化且數組長度<CPU核心數,且collide認爲無碰撞,則設置有碰撞
                else if (!collide)
                    collide = true;
                //執行到這裏,說明數組沒變化,且有碰撞,則需要擴容
                else if (
                        //無鎖
                        cellsBusy == 0 &&
                                //嘗試加鎖
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                    try {
                        //counterCells沒發生變化
                        if (counterCells == as) {// Expand table unless stale
                            //擴容counterCells一倍容量
                            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);
            }
            //執行到這裏說明counterCells爲空,判斷鎖是否未被佔用,嘗試加鎖
            else if (cellsBusy == 0 && counterCells == as &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                boolean init = false;
                try {                           // Initialize table
                    //再次判斷counterCells是否有變化
                    if (counterCells == as) {
                        //新建長度2的數組
                        CounterCell[] rs = new CounterCell[2];
                        rs[h & 1] = new CounterCell(x);
                        counterCells = rs;
                        //標記初始化完成
                        init = true;
                    }
                } finally {
                    //釋放鎖
                    cellsBusy = 0;
                }
                //跳出循環
                if (init)
                    break;
            }
            //數組爲空,搶鎖失敗,則嘗試直接累加baseCount值
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                break;                          // Fall back on using base
        }
    }

replace#remove

    public V remove(Object key) {
        return replaceNode(key, null, null);
    }
 
    /**
     * {@inheritDoc}
     *
     * @throws NullPointerException if any of the arguments are null
     * key映射有值,值爲oldValue,則更新
     */
    public boolean replace(K key, V oldValue, V newValue) {
        if (key == null || oldValue == null || newValue == null)
            throw new NullPointerException();
        return replaceNode(key, newValue, oldValue) != null;
    }


    final V replaceNode(Object key, V value, Object cv) {
        int hash = spread(key.hashCode());
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //空判斷
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            //正在擴容,則加入擴容隊伍
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                boolean validated = false;
                synchronized (f) {
                    //雙重檢查加鎖,DCL
                    if (tabAt(tab, i) == f) {
                        //對鏈表的處理
                        if (fh >= 0) {
                            validated = true;
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                //匹配key
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    //匹配value
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        oldVal = ev;
                                        //修改
                                        if (value != null)
                                            e.val = value;
                                        //value=null,說明執行刪除操作
                                        else if (pred != null)
                                            pred.next = e.next;
                                        //當匹配的值是first節點的處理
                                        else
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                //臨時保存上一個節點,便於執行刪除節點操作
                                pred = e;
                                //執行最後一個節點,則退出
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        //紅黑樹的處理
                        else if (f instanceof TreeBin) {
                            validated = true;
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> r, p;
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                                V pv = p.val;
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    oldVal = pv;
                                    if (value != null)
                                        p.val = value;
                                    else if (t.removeTreeNode(p))
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                if (validated) {
                    if (oldVal != null) {
                        if (value == null)
                            //記數-1
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }

總結:

什麼時候擴容?

  1. 單節點容量>=8且容量<64,則擴容一倍
  2. 當數組中元素達到了sizeCtl的數量的時候,則會調用transfer方法來進行擴容

沒有實現對map進行加鎖來執行獨佔訪問,因爲採用了分段鎖,所以無法使用客戶端加鎖來創建新的原子操作,如若沒有則添加之內操作.

JDK1.8放棄分段鎖

段Segment繼承了重入鎖ReentrantLock,有了鎖的功能,每個鎖控制的是一段,當每個Segment越來越大時,鎖的粒度就變得有些大了。
分段鎖的優勢在於保證在操作不同段 map 的時候可以併發執行,操作同段 map 的時候,進行鎖的競爭和等待。這相對於直接對整個map同步synchronized是有優勢的。
缺點在於分成很多段時會比較浪費內存空間(不連續,碎片化); 操作map時競爭同一個分段鎖的概率非常小時,分段鎖反而會造成更新等操作的長時間等待; 當某個段很大時,分段鎖的性能會下降。

jdk1.8的map實現

和hashmap一樣,jdk 1.8中ConcurrentHashmap採用的底層數據結構爲數組+鏈表+紅黑樹的形式。數組可以擴容,鏈表可以轉化爲紅黑樹。

爲什麼不用ReentrantLock而用synchronized ?

減少內存開銷:如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步。
內部優化:synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。

多個線程又是如何同步處理的呢?

  1. 同步處理主要是通過Synchronized和unsafe兩種方式來完成的。
  2. 在取得sizeCtl、某個位置的Node的時候,使用的都是unsafe的方法,來達到併發安全的目的
    當需要在某個位置設置節點的時候,則會通過Synchronized的同步機制來鎖定該位置的節點。
  3. 在數組擴容的時候,則通過處理的步長和fwd節點來達到併發安全的目的,通過設置hash值爲MOVED
  4. 當把某個位置的節點複製到擴張後的table的時候,也通過Synchronized的同步機制來保證現程安全
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章