1. 程式人生 > >ConcurrentHashMap原始碼剖析(1.8版本)

ConcurrentHashMap原始碼剖析(1.8版本)

ConcurrentHashMap原始碼剖析

基於jdk1.8。

資料結構

僅列出最重要的程式碼片段

Node

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        /**
         * 子類中重寫了這個方法,這裡的find實現了在連結串列中查詢hash值等於h且key等於k的節點
         */
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; } }

ForwardingNode

     /**
     * A node inserted at head of bins during transfer operations.
     */
     // 並不是我們傳統的包含key-value的節點,只是一個標誌節點,並且指向nextTable,提供find方法而已。生命週期:僅存活於擴容操作且bin不為null時,一定會出現在每個bin的首位。
static final class ForwardingNode<K,V> extends Node<K,V> { final Node<K,V>[] nextTable; ForwardingNode(Node<K,V>[] tab) { super(MOVED, null, null, null); this.nextTable = tab; } Node<K,V> find(int h, Object k) { // loop to avoid arbitrarily deep recursion on forwarding nodes outer: for (Node<K,V>[] tab = nextTable;;) { Node<K,V> e; int n; if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null)// 頭結點存在e中 return null; for (;;) { // 檢查頭結點是否為要找的node int eh; K ek; if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) return e; // 如果頭結點不是要找的節點 if (eh < 0) { // 頭結點hash值小於0 // 如果頭結點是ForwardingNode,那麼繼續下一個ForwardingNode的find邏輯 if (e instanceof ForwardingNode) { tab = ((ForwardingNode<K,V>)e).nextTable; continue outer; } // 如果頭結點不是ForwardingNode,就進行相應的find邏輯 else return e.find(h, k); } // 查詢到尾部仍然沒有找到對應的node if ((e = e.next) == null) return null; } } } }

TreeNode

紅黑樹中的節點類,值得注意的是:TreeNode可用於構造雙向連結串列,Node包含next成員,同時,TreeNode加入了prev成員。

static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;

        TreeNode(int hash, K key, V val, Node<K,V> next,
                 TreeNode<K,V> parent) {
            super(hash, key, val, next);
            this.parent = parent;
        }

        Node<K,V> find(int h, Object k) {
            return findTreeNode(h, k, null);
        }

        final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
            if (k != null) {
                TreeNode<K,V> p = this;
                do  {
                    int ph, dir; K pk; TreeNode<K,V> q;
                    TreeNode<K,V> pl = p.left, pr = p.right;
                    if ((ph = p.hash) > h)
                        p = pl;
                    else if (ph < h)
                        p = pr;
                    else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                        return p;
                    // hash值相等,key不等,左子樹不存在,搜尋右子樹
                    else if (pl == null)
                        p = pr;
                    // hash值相等,key不等,右子樹不存在,搜尋左子樹
                    else if (pr == null)
                        p = pl;
                   /*
                    * comparableClassFor的作用是:
                    * 如果k實現了Comparable介面,返回k的Class,
                    * 否則返回null。
                    * compareComparables的作用是:
                    * 將k與pk做比較
                    * 如果TreeNode的Key可以作比較,就可以繼續在樹中搜索
                    */
                    else if ((kc != null ||
                              (kc = comparableClassFor(k)) != null) &&
                             (dir = compareComparables(kc, k, pk)) != 0)
                        p = (dir < 0) ? pl : pr;
                    // 由於hash相等,key無法做比較,因此先在右子樹中找
                    else if ((q = pr.findTreeNode(h, k, kc)) != null)
                        return q;
                    // 右子樹沒有找到,繼續從當前的節點的左子樹中找
                    else
                        p = pl;
                } while (p != null);
            }
            return null;
        }
    }

TreeBin

TreeBin封裝了紅黑樹的邏輯,有關紅黑樹, 可以參考的資料有《Algorithm》網站 以及 中文翻譯

附文章中提到的紅黑樹旋轉的動圖與TreeBin中的rotateLeft、rotateRight程式碼片段幫助理解。

左旋:
rotateLeft

對應程式碼

    static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            TreeNode<K,V> r, pp, rl;
            // p是圖中的E節點,r是圖中的S節點
            if (p != null && (r = p.right) != null) {
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                // p是根節點,則根節點需要變化
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                // p不是根節點,如果p是pp的左節點,就更新pp的left
                else if (pp.left == p)
                    pp.left = r;
                else
                    pp.right = r;
                // 把p放在左子樹中
                r.left = p;
                p.parent = r;
            }
            return root;
        }

右旋:
rotateRight

對應程式碼


        static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
            TreeNode<K,V> l, pp, lr;
            // p是途中的S,l是圖中的E
            if (p != null && (l = p.left) != null) {
                if ((lr = p.left = l.right) != null)
                    lr.parent = p;
                // p是根節點,則根節點需要變化
                if ((pp = l.parent = p.parent) == null)
                    (root = l).red = false;
                else if (pp.right == p)
                    pp.right = l;
                else
                    pp.left = l;
                l.right = p;
                p.parent = l;
            }
            return root;
        }

僅列出Treebin資料成員以及部分方法:

// 維護了一個紅黑樹
static final class TreeBin<K,V> extends Node<K,V> {
        TreeNode<K,V> root;
        // 連結串列頭結點,每次都將新節點插入到連結串列的頭部,成為新的頭結點
        // 因此該連結串列中節點的順序與插入順序相反
        volatile TreeNode<K,V> first;
        volatile Thread waiter;
        volatile int lockState;

         /**
         * 返回匹配的node或者沒有匹配的就返回null. 在樹中從根節點開始比較,
         * 當鎖不可用的時候進行線性搜尋
         */
        final Node<K,V> find(int h, Object k) {
            if (k != null) {
                for (Node<K,V> e = first; e != null; ) {
                    int s; K ek;
                    // 鎖不可用,lockState包含了WAITER或者WRITER標誌位
                    if (((s = lockState) & (WAITER|WRITER)) != 0) {
                        if (e.hash == h &&
                            ((ek = e.key) == k || (ek != null && k.equals(ek))))
                            return e;
                        e = e.next;
                    }
                    // 鎖可用,當前物件設定為READER狀態
                    else if (U.compareAndSwapInt(this, LOCKSTATE, s,
                                                 s + READER)) {
                        TreeNode<K,V> r, p;
                        try {
                            // 在樹中查詢匹配的節點
                            p = ((r = root) == null ? null :
                                 r.findTreeNode(h, k, null));
                        } finally {
                            Thread w;
                            // 取消當前鎖的READER狀態
                            if (U.getAndAddInt(this, LOCKSTATE, -READER) ==
                                (READER|WAITER) && (w = waiter) != null)
                                LockSupport.unpark(w);
                        }
                        return p;
                    }
                }
            }
            return null;
        }

         // 尋找或者新增一個節點
        final TreeNode<K,V> putTreeVal(int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                //  紅黑樹是空,直接插入到根節點
                if (p == null) {
                    first = root = new TreeNode<K,V>(h, k, v, null, null);
                    break;
                }
                // 根據hash值設定標記位
                else if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                // hash值相同,並且k與pk相等(equals),直接返回
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                // hash相同,p與pk不equals,但是按照比較介面發現p與pk相等
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null))
                            return q;
                    }
                    // 根據一種確定的規則來進行比較,至於規則本身具體是什麼病不重要
                    dir = tieBreakOrder(k, pk);
                }

                // 程式執行到這裡,說明當前節點不匹配,但子樹中可能會有匹配的Node
                TreeNode<K,V> xp = p;
                // 根據大小關係移動p到左子樹或者右子樹
                // 如果滿足p為null,則說明樹中沒有節點能與之匹配,應當在p位置插入新節點,然後維護紅黑樹的性質
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    TreeNode<K,V> x, f = first;
                    first = x = new TreeNode<K,V>(h, k, v, f, xp);
                    if (f != null)
                        f.prev = x;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    // 優先將新節點染為紅色
                    if (!xp.red)
                        x.red = true;
                    else {
                        lockRoot();
                        try {
                            root = balanceInsertion(root, x);
                        } finally {
                            unlockRoot();
                        }
                    }
                    break;
                }
            }
            assert checkInvariants(root);
            return null;
        }
}

// 紅黑樹的平衡插入
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
            x.red = true; // 將x染成紅色
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                // 根節點必須是黑色
                if ((xp = x.parent) == null) {
                    x.red = false;
                    return x;
                }
                // 父節點是黑色或者父節點是根節點
                // 總之父節點是黑色,那麼不會違反紅黑樹性質
                // 不需要調整結構,直接返回根節點即可
                else if (!xp.red || (xpp = xp.parent) == null)
                    return root;
                // 父節點是紅色(需要調整),且在祖父節點的左子樹中
                if (xp == (xppl = xpp.left)) {
                    // 因為父節點為紅色,所以xppr必須是紅色或空,不可能是黑色
                    // 祖父節點的右節點為紅色
                    if ((xppr = xpp.right) != null && xppr.red) {

                   /**
                     *     黑                  紅
                     *    /  \    (染色後)    / \
                     *   紅   紅    ->        黑  黑
                     *  /                   /
                     * 紅                  紅
                     * 
                     * 可見通過調整顏色後,子樹不需要旋轉就可以滿足紅黑樹的性質
                     * 但由於xpp變成了紅色,有可能違反紅黑樹性質,仍然需要向上調整
                    */

                        xppr.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    // xppr是空
                    else {
                       /**
                        *      黑
                        *     /
                        *    紅 
                        *      \
                        *       紅
                        */
                        if (x == xp.right) {
                            /**
                                * 進行左旋操作,變為以下形式,
                                * 可以看出此時任然違反紅黑樹的性質,
                                * 然而x仍然指向了最下面衝突的紅色節點,
                                * 此處僅僅調整了樹的形狀
                                *
                                *      黑
                                *     /
                                *    紅
                                *   /
                                *  紅
                                */
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        /*
                         * 由於調整了樹的形狀,因此此時樹一定長成這個樣子
                         * 
                         *      黑
                         *     /
                         *    紅
                         *   /
                         *  紅
                         * 
                         * 在染色並右旋之後,變為
                         * 
                         *    黑
                         *   /  \
                         * 紅     紅
                         */
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;

                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                // x在祖父節點的右子樹中,這種情況與x在祖父節點左子樹中類似,因此不多作解釋,不明白的話類比即可。
                else {
                /**
                *   黑                    紅
                *  /  \     (染色後)      / \
                * 紅    紅   ->         黑   黑
                *        \                    \
                *         紅                   紅色
                */
                    if (xppl != null && xppl.red) {
                        xppl.red = false;
                        xp.red = false;
                        xpp.red = true;
                        x = xpp;
                    }
                    else {
                        if (x == xp.left) {
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        if (xp != null) {
                            xp.red = false;
                            if (xpp != null) {
                                xpp.red = true;
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }

核心成員

    // ForwardingNode的hash值都是-1
    static final int MOVED     = -1; 
    // Treebin的hash值是-1
    static final int TREEBIN   = -2; 

    /**
     * 在第一次insert的時候才進行初始化(延遲初始化)
     * Size總是2的冪. 直接通過迭代器訪問.
     */
    transient volatile Node<K,V>[] table;

    // nextTable的用途:只有在擴容時是非空的
    private transient volatile Node<K,V>[] nextTable;

    /**
     * Base counter value, used mainly when there is no contention,
     * but also as a fallback during table initialization
     * races. Updated via CAS.
     */
    private transient volatile long baseCount;

    /**
     * sizeCtl是控制識別符號,不同的值表示不同的意義。
     * -1代表正在初始化; 
     * -(1+有效擴容執行緒的數量),比如,-N 表示有N-1個執行緒正在進行擴容操作;
     * 0 表示還未進行初始化
     * 正數代表初始化或下一次進行擴容的大小,類似於擴容閾值。它的值始終是當前ConcurrentHashMap容量的0.75倍,這與loadfactor是對應的。實際容量>=sizeCtl,則擴容。
     */
    private transient volatile int sizeCtl;


     // 擴容的時候,next陣列下標+1
    private transient volatile int transferIndex;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
     */
    private transient volatile int cellsBusy;

    /**
     * Table of counter cells. When non-null, size is a power of 2.
     */
    private transient volatile CounterCell[] counterCells;

    // 檢視
    private transient KeySetView<K,V> keySet;
    private transient ValuesView<K,V> values;
    private transient EntrySetView<K,V> entrySet;

核心函式

ConcurrentHashMap(int initialCapacity)

之所以列出這個函式,是因為這個函式初始化了sizeCtl,並且可以看出table在這裡並沒有被初始化,而是在插入元素的時候進行延遲初始化。
我們要注意的是table的長度始終是2的冪,sizeCtl的值為正數時表示擴容的最小閥值。

 // 需要注意的是,構造了一個能夠容納initialCapacity個元素的物件,
 // 但實際table的大小比1.5倍的initialCapacity還多
 public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        // 保證cap是2的冪,其中tableSizeFor返回大於入參的最小的2的冪
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

initTable

     // 初始化table,使用sizeCtl記錄table的容量
     // 為了保證併發訪問不會出現衝突,使用了Unsafe的CAS操作
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        // tab是空的
        while ((tab = table) == null || tab.length == 0) {
            // 如果已經初始化過
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // 退出初始化陣列的競爭; just spin
            // 如果沒有執行緒在初始化,將sizeCtl設定為-1,表示正在初始化
            // CAS操作,由此可見sizeCtl維護table的併發訪問
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    // 再次檢查table是否為空
                    if ((tab = table) == null || tab.length == 0) {
                        // 計算分配多少個Node
                        // sc大於0的時候表示要分配的大小
                        // 否則預設分配16個node
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        // 下次擴容的最小閥值0.75*n
                        // 注意0.75 * n < n,而且它很可能不是2的冪,
                        // 例如n = 16, 則sc = 12;
                        // 因此這個閥值在後續擴容情況下實際上不會成為陣列的容量值,但它可以用來能保證使用者提供了容量大小時,能夠容納使用者要求數目的元素。
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

put

put過程的描述:

為表述方便,用符號i 來表示 (n - 1) & hash,用newNode表示使用key,value建立的節點

loop:
{
    if table == null
    {
        初始化一個預設長度為16的陣列
    }
    else table[i] == null
    {   
        table[i] = newNode
    }
    else hash == -1table[i]是ForwardingNode
    {
        進行整合表的操作
    }
    else
    {
        if hash >= 0table[i]不是特殊Node(連結串列中的Node)
        {
            將newNode插入到連結串列中
        }
        else table[i]是TreeBin
        {
             newNode插入到TreeNode中
        }
    }
    addCount(1L, binCount);
}

通過研讀程式碼,發現Doug Lea使用了一種有效且高效的技巧:
在迴圈裡面巢狀使用CAS操作。這種技巧把臨界區變得很小,因此比較高效。

put原始碼如下:

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

/** put和putIfAbsent都是通過呼叫putVal方法來實現的*/
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // ConcurrentHashMap不支援key和value是null
        if (key == null || value == null) throw new NullPointerException();
        // 獲取hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // case 1:tab為null,需要初始化tab
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // case 2: 沒有任何節點hash值與當前要插入的節點相同
            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
            }
            // case 3: 當遇到表連線點時,需要進行整合表的操作
            // 需要注意的是,遇到連線點的時候,並沒有插入新節點,僅僅幫助擴容,因為當前執行緒迫切需要儘快插入新節點,只能等待擴容完畢才有可能插入新節點
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            // case 4: 找到對應於hash值的連結串列首節點,且該節點不是連線節點
            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的node,根據onlyIfAbsent來更新node的值
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                // 如果一直到連結串列的尾部都沒有找到任何node的key與key相同,就插入到連結串列的尾部
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        // 如果該節點是TreeBin,就插入到TreeBin中
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            // 當存在相同的key時,putTreeVal不會修改那個TreeNode,而是返回給p,由onlyIfAbsent決定是否修改p.val
                            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;
                }
            }
        }
        // 新增計數,如有需要,擴容
        addCount(1L, binCount);
        return null;
    }

    // 給tab[i]賦值
    // 如果tab[i]等於c,就將tab[i]與v交換數值
    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);
    }

    /**
    * 協助擴容方法。
    * 多執行緒下,當前執行緒檢測到其他執行緒正進行擴容操作,則協助其一起擴容;
    *(只有這種情況會被呼叫)從某種程度上說,其“優先順序”很高,
    * 只要檢測到擴容,就會放下其他工作,先擴容。
    * 呼叫之前,nextTable一定已存在。
    */
    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        // 如果f是tab中的連線節點,並且它所連線的table非空
        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) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

get

get方法比較簡單,沒有使用鎖,而是用Unsafe來保證獲取的頭結點是volatile的

 public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        // 獲取hash值h
        int h = spread(key.hashCode());
        // tab只是儲存了hash值相同的頭結點
        if ((tab = table) != null && (n = tab.length) > 0 && // table裡面有元素
            (e = tabAt(tab, (n - 1) & h)) != null) {// 根據h來獲取頭結點e
            // hash值相同,如果找到key,直接返回
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // todo:看一下hash值什麼時候小於0
            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;
    }

//tableAt方法使用了Unsafe物件來獲取陣列中下標為i的物件
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        // 第i個元素實際地址i * (2^ASHIFT) + ABASE
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

treeifyBin

     // 如果tab的長度很小,小於64個,就嘗試進行擴容為兩倍,
     // 否則就將以tab[index]開頭的連結串列轉換為Treebin
    private final void treeifyBin(Node<K,V>[] tab, int index) {
        Node<K,V> b; int n, sc;
        if (tab != null) {
            // tab的長度小於64,就嘗試進行擴容
            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;
                        // 這個迴圈建立了TreeNode中的雙向連結串列,hd儲存了雙向連結串列的頭結點
                        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);
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

tryPresize

    // 嘗試擴容使它能放size個元素
    private final void tryPresize(int size) {
        // 計算擴容後的數量
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
            tableSizeFor(size + (size >>> 1) + 1);
        int sc;
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            // 如果tab是空的,直接擴容
            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;
                            // 下次擴容的容量閥值是0.75 * n
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                }
            }
            // 容量已經夠用,不需要進行擴容;或者容量太大,無法進行擴容。
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            // 仍然需要擴容
            else if (tab == table) {
                int rs = resizeStamp(n);
                // todo:不是很懂為什麼會出現 sc < 0 ?先看一下transfer的實現
                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);
            }
        }
    }

transfer

虛擬碼:

n = table.length

nextTable = new Node[2 * n]

forwardingNode = new ForwardingNode

forwardingNode.nextTable = nextTable;

for(table[i] : table)
{
    for(p = table[i]; p != null ; p = p.next)
    {
        if(p.hash & n == 0)
            將p放入nextTable[i]的資料集合中
        else
            將p放入nextTable[i+n]的資料集合中
    }
    table[i] = forwardingNode;
}

table = nextTable;

nextTable = null;

數學公式:

已知:n = 2 ^ k , hash & (n-1) = i,顯而易見:
(1)若 hash & n = 0, 則 hash &(2*n - 1) = i ;
(2)若 hash & n != 0, 則 hash&(2*n - 1) = i + n。

原始碼在此:

     // 把table中所有的Node放入新的table中
    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; // 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;
        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;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            
            
           

相關推薦

ConcurrentHashMap原始碼剖析1.8版本

ConcurrentHashMap原始碼剖析 基於jdk1.8。 資料結構 僅列出最重要的程式碼片段 Node static class Node<K,V> implements Map.Entry<K,V&

linux線上安裝JDK1.8版本

線上下載JDK下載讀取條:檢視當前資料夾下是否有JDK安裝包:新增執行許可權:命令:chmod +x jdk-8u131-linux-x64.rpm 執行rpm進行安裝命令:rpm -ivh jdk-8u131-linux-x64.rpm檢視JDK是否安裝成功命令:java

linux在線安裝JDK1.8版本

8.0 oracle down -h 系統 spa and acl water 在線下載JDK 命令: wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-secu

ArrayBlockingQueue原始碼閱讀1.8

ArrayBlockingQueue原始碼閱讀 1、ArrayBlockingQueue類結構   public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E

ConcurrentHashMap原始碼解析JDK1.8

package java.util.concurrent; import java.io.ObjectStreamField; import java.io.Serializable; import java.lang.reflect.ParameterizedType;

libgo 原始碼剖析1. libgo簡介與排程淺談

閒談 協程是一個很早的概念了,早些年的遊戲行業中已經大規模地在使用,像lua、go這些語言中的協程原語已經相對比較完善了,一般來說直接使用就好,但是在系統後臺開發上,出現的時間並不長。我是做C++方向的後臺開發,目前國內一些公司也開源了一些C++協程庫,但目前來說,還是在逐步完善的階段。最早接觸的C++協程

分散式鎖原始碼剖析1 Redisson實現非公平分散式鎖

Redisson分散式鎖原始碼剖析(非公平鎖) maven配置檔案: <dependency> <groupId>org.redisson</groupId> <artifactId>redisso

linux centos安裝jdk1.8.xxxx

Linux下安裝jdk8步驟詳述 作為Java開發人員,在Linux下安裝一些開發工具是必備技能,本文以安裝jdk為例,詳細記錄了每一步的操作命令,以供參考。 0.下載jdk8 登入網址:http://www.oracle.com/technetwork/java/javase/down

LinuxCentOS 764位系統下安裝Pymol1.8.6

PyMOL簡介 PyMOL是一款生物大分子三維結構顯示軟體,其中“Py”是指此軟體使用Python語言編寫,“MOL”是指Molecule。 PyMOL官網是http://www.PyMOL.or

better-scroll 輪播1.0+版本

最近因為vue不是很熟練,所以看了慕課網的移動端音樂app。 但是在跟著做輪播圖的時候發現很多問題,迴圈不出來,自動輪播不出來等問題,發現是版本不對。解決完之後做個mark,如果你們也遇到這樣的問題,希望對你們有用 首先是slider.vue <tem

word2vec原始碼解析註釋合理版本

word2vec詞向量學習筆記 一、使用原版word2vec工具訓練 1、英文編譯測試 git clone https://github.com/hjimce/word2vec.git   (2)編譯:make (3)下載測試資料http://mattmahoney.NET/dc/text

Glide 系列-2:主流程原始碼分析4.8.0

Glide 是 Android 端比較常用的圖片載入框架,這裡我們就不再介紹它的基礎的使用方式。你可以通過檢視其官方文件學習其基礎使用。這裡,我們給出一個 Glide 的最基本的使用示例,並以此來研究這個整個過程發生了什麼: Glide.with(fragment).load(myU

舊版spark1.6版本 將rdd動態轉為dataframe

前言 舊版本spark不能直接讀取csv轉為df,沒有spark.read.option(“header”, “true”).csv這麼簡單的方法直接將第一行作為df的列名,只能現將資料讀取為rdd,然後通過map和todf方法轉為df,如果csv的列數很

JDK原始碼學習jdk1.8.0_20

集合框架 ArrayList 基於jdk1.8.0_20 關注點 結論 ArrayList是否允許空 允許 ArrayList是否允許重複資料 允許 ArrayList是否有序 有序 Arra

Android Studio1.3版本設定Gradle代理的正確姿勢

看了下這可是好東西,馬蛋不是每個人都會用vpn的。為了生存不得不去使用Android Studio,然後這個卵東西又是升級更新的特別快,然後真的要去更新還特別 的麻煩。 遭遇 手賤把自己電腦的Android Studio升級到最新的1.3 Preview。然後上

kafka0.8版本刪除主題沒有在配置文件中配置的情況下

per ble 配置文件 top 標記 屬性 con conf zkcli 在沒有配置kafka 刪除屬性的情況下 使用刪除主題命令 ./bin/kafka-topics.sh --delete --zookeeper 192.168.28.131:2181,192.

Activity啟動過程原始碼分析Android 8.0

Activity啟動過程原始碼分析 本文來Activity的啟動流程,一般我們都是通過startActivity或startActivityForResult來啟動目標activity,那麼我們就由此出發探究系統是如何實現目標activity的啟動的。 startActivity(new Intent(con

ConcurrentHashMap原始碼探究 (JDK 1.8)

很早就知道在多執行緒環境中,HashMap不安全,應該使用ConcurrentHashMap等併發安全的容器代替,對於ConcurrentHashMap也有一定的瞭解,但是由於沒有深入到原始碼層面,很多理解都是浮於表面,稍微深一點的東西就不是很懂。這兩天終於下定決心將ConcurrentHashMap的原始碼

spring原始碼學習5.1.0版本——Bean的初始化

目錄   前言 createBean 有自定義TargetSource代理類的生成 resolveBeforeInstantiation applyBeanPostProcessorsBeforeInstantiation postProcessBeforeIn

spring原始碼學習5.1.0版本——Bean的初始化

目錄   前言 源頭 preInstantiateSingletons方法 getBean(String beanName) doGetBean getObjectForBeanInstance getObjectFromFactoryBean doGe