ConcurrentHashMap(一):常量,成員變數,靜態程式碼塊,內部類,spread函式,tabAt函式等詳解

土撥鼠飼養員發表於2020-10-19

雜湊表

Node節點(K-V)所放的位置:key的雜湊值 & 陣列.len-1

1.假如計算的下標為如圖5,就會來到連結串列結構中,從頭開始檢查,看是否有key一致的節點,如果一致就進行替換,如果沒有一致的節點就插入到末尾(要判斷是否樹化)

2.如果計算的下標為如圖的10,可以看見是TreeBin節點(代表當前桶位已經樹化成紅黑樹——可以提高查詢的效率),其中TreeBin節點中維護了兩個結構:紅黑樹結構和連結串列結構,先會插入到TreeNode連結串列結構,然後插入到TreeNode紅黑樹結構中(需要平衡操作)

3.如圖的FWD節點 (擴容的時候可以用) ,處理完一個桶位之後,引用需要指向FWD節點,其他執行緒來到雜湊表時:1.如果是put操作,到FWD節點會幫你一起擴容。2.如果是get操作(FWD中儲存了新表、擴容後表的引用)會到新表中執行查詢操作。

4.擴容的順序是從後往前遷移的——方便迭代的讀,就是從前往後讀的話,讀到後面失去的數可以到新表去讀(如果從低到高 容易衝突)

1_深入理解ConcurrentHashMap 常量

    /* ---------------- Constants -------------- */

    /**
     * The largest possible table capacity.  This value must be
     * exactly 1<<30 to stay within Java array allocation and indexing
     * bounds for power of two table sizes, and is further required
     * because the top two bits of 32bit hash fields are used for
     * control purposes.
     * 雜湊表陣列最大限制
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The default initial table capacity.  Must be a power of 2
     * (i.e., at least 1) and at most MAXIMUM_CAPACITY.
     * 雜湊表預設值
     */
    private static final int DEFAULT_CAPACITY = 16;

    /**
     * The largest possible (non-power of two) array size.
     * Needed by toArray and related methods.
     */
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * The default concurrency level for this table. Unused but
     * defined for compatibility with previous versions of this class.
     * 併發級別,jdk1.7遺留下來的,1.8只有在初始化的時候用了一用。
     * 不代表併發級別。
     */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

    /**
     * The load factor for this table. Overrides of this value in
     * constructors affect only the initial table capacity.  The
     * actual floating point value isn't normally used -- it is
     * simpler to use expressions such as {@code n - (n >>> 2)} for
     * the associated resizing threshold.
     * 負載因子,JDK1.8中 ConcurrentHashMap 是固定值
     */
    private static final float LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2, and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     * 樹化閾值,指定桶位 連結串列長度達到8的話,有可能發生樹化操作。
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     * 紅黑樹轉化為連結串列的閾值
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * The value should be at least 4 * TREEIFY_THRESHOLD to avoid
     * conflicts between resizing and treeification thresholds.
     * 聯合TREEIFY_THRESHOLD控制桶位是否樹化,只有當table陣列長度達到64且 某個桶位 中的連結串列長度達到8,才會真正樹化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    /**
     * Minimum number of rebinnings per transfer step. Ranges are
     * subdivided to allow multiple resizer threads.  This value
     * serves as a lower bound to avoid resizers encountering
     * excessive memory contention.  The value should be at least
     * DEFAULT_CAPACITY.
     * 執行緒遷移資料最小步長,控制執行緒遷移任務最小區間一個值
     */
    private static final int MIN_TRANSFER_STRIDE = 16;

    /**
     * The number of bits used for generation stamp in sizeCtl.
     * Must be at least 6 for 32bit arrays.
     * 擴容相關,計算擴容時生成的一個標識戳(雖然沒有用final修飾,但是全文沒有修改他,不改變)
     * 不管什麼執行緒來 16擴容到32都是不變的
     */
    private static int RESIZE_STAMP_BITS = 16;

    /**
     * The maximum number of threads that can help resize.
     * Must fit in 32 - RESIZE_STAMP_BITS bits.
     * 65535 表示併發擴容最多執行緒數
     */
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;

    /**
     * The bit shift for recording size stamp in sizeCtl.
     * 擴容相關
     */
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

    /*
     * Encodings for Node hash fields. See above for explanation.
     */
    //node節點的hash為-1時,表示當前節點是FWD節點,已經遷移了
    static final int MOVED     = -1; // hash for forwarding nodes
    //node節點的hash為-2時,表示當前節點已經樹化,
    // 表示當前節點為TreeBin節點,TreeBin節點代理操作紅黑樹
    static final int TREEBIN   = -2; // hash for roots of trees
    // ReservationNode的hash值
    static final int RESERVED  = -3; // hash for transient reservations
    //0x7fffffff =》0111 1111 1111 1111 1111 1111 1111 1111
    //可以將一個負數 位與運算後得到正數,但是不是絕對值
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

    /** Number of CPUS, to place bounds on some sizings.
     * 當前系統的cpu數量
     */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /** For serialization compatibility.
     * JDK1.8序列化 為了相容jdk1.7的ConcurrentHashMap儲存的
     */
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("segments", Segment[].class),
        new ObjectStreamField("segmentMask", Integer.TYPE),
        new ObjectStreamField("segmentShift", Integer.TYPE)
    };

2_深入理解ConcurrentHashMap 成員變數

    /* ---------------- Fields -------------- */

    /**
     * The array of bins. Lazily initialized upon first insertion.
     * Size is always a power of two. Accessed directly by iterators.
     * 雜湊表,長度一定是2次方數
     */
    transient volatile Node<K,V>[] table;

    /**
     * The next table to use; non-null only while resizing.
     * 擴容過程中,會將擴容中的新table 賦值給nextTable 保持引用,擴容結束之後,這裡會被設定為Null
     */
    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.
     * 化整為零
     * LongAdder 中的 baseCount 未發生競爭時 或者 當前LongAdder處於加鎖狀態時,增量累到到baseCount中
     */
    private transient volatile long baseCount;

    /**
     * Table initialization and resizing control.  When negative, the
     * table is being initialized or resized: -1 for initialization,
     * else -(1 + the number of active resizing threads).  Otherwise,
     * when table is null, holds the initial table size to use upon
     * creation, or 0 for default. After initialization, holds the
     * next element count value upon which to resize the table.
     * sizeCtl < 0
     * 1. -1 表示當前table正在初始化(有執行緒在建立table陣列),當前執行緒需要自旋等待..
     * 2.表示當前table陣列正在進行擴容 ,高16位表示:擴容的標識戳   低16位表示:(1 + nThread) 當前參與併發擴容的執行緒數量
     *
     * sizeCtl = 0,表示建立table陣列時 使用DEFAULT_CAPACITY為大小
     *
     * sizeCtl > 0
     *
     * 1. 如果table未初始化,表示初始化大小
     * 2. 如果table已經初始化,表示下次擴容時的 觸發條件(閾值)
     */
    private transient volatile int sizeCtl;

    /**
     * The next table index (plus one) to split while resizing.
     * 擴容過程中,記錄當前進度。所有執行緒都需要從transferIndex中分配區間任務,去執行自己的任務。
     */
    private transient volatile int transferIndex;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
     * LongAdder中的cellsBuzy 0表示當前LongAdder物件無鎖狀態,1表示當前LongAdder物件加鎖狀態)
     * 只有一個物件能持有加鎖狀態
     *
     */
    private transient volatile int cellsBusy;

    /**
     * Table of counter cells. When non-null, size is a power of 2.
     * LongAdder中的cells陣列,當baseCount發生競爭後,會建立cells陣列,
     * 執行緒會通過計算hash值 取到 自己的cell ,將增量累加到指定cell中
     * 總數 = sum(cells) + baseCount
     */
    private transient volatile CounterCell[] counterCells;

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


3_分解靜態程式碼塊

    // Unsafe mechanics
    private static final sun.misc.Unsafe U;
    /**表示sizeCtl屬性在ConcurrentHashMap中記憶體偏移地址*/
    private static final long SIZECTL;
    /**表示transferIndex屬性在ConcurrentHashMap中記憶體偏移地址*/
    private static final long TRANSFERINDEX;
    /**表示baseCount屬性在ConcurrentHashMap中記憶體偏移地址*/
    private static final long BASECOUNT;
    /**表示cellsBusy屬性在ConcurrentHashMap中記憶體偏移地址*/
    private static final long CELLSBUSY;
    /**表示cellValue屬性在CounterCell中記憶體偏移地址*/
    private static final long CELLVALUE;
    /**表示陣列第一個元素的偏移地址*/
    private static final long ABASE;
    private static final int ASHIFT;

    static {
        try {
            U = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ConcurrentHashMap.class;
            SIZECTL = U.objectFieldOffset
                (k.getDeclaredField("sizeCtl"));
            TRANSFERINDEX = U.objectFieldOffset
                (k.getDeclaredField("transferIndex"));
            BASECOUNT = U.objectFieldOffset
                (k.getDeclaredField("baseCount"));
            CELLSBUSY = U.objectFieldOffset
                (k.getDeclaredField("cellsBusy"));
            Class<?> ck = CounterCell.class;
            CELLVALUE = U.objectFieldOffset
                (ck.getDeclaredField("value"));
            Class<?> ak = Node[].class;
            ABASE = U.arrayBaseOffset(ak);
            //表示陣列單元所佔用空間大小,scale 表示Node[]陣列中每一個單元所佔用空間大小
            int scale = U.arrayIndexScale(ak);
            //1 0000 & 0 1111 = 0
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            //numberOfLeadingZeros() 這個方法是返回當前數值轉換為二進位制後,從高位到低位開始統計,看有多少個0連續在一塊。
            //8 => 1000 numberOfLeadingZeros(8) = 28
            //int是4個長度 100  32-3=29
            //4 => 100 numberOfLeadingZeros(4) = 29
            //ASHIFT = 31 - 29 = 2 ??  scale是區域性變數
            //下標為5的資料
            //ABASE + (5 << ASHIFT)
            ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

4_分解內部類 Node、TreeNode、ForwardingNode

Node

    static class Node<K,V> implements Map.Entry<K,V> {
        //這個hash的K的hash經過一次擾動運算得出來的
        final int hash;
        final K key; //執行緒安全不能修改
        volatile V val;  //val可能修改,保持執行緒可見性
        volatile Node<K,V> next; //結構可能變化 保持可見性

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

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         * 連結串列情況下用不到,當前桶位變成 treebin  fwd節點會用到
         */
        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

    /**
     * Nodes for use in TreeBins
     */
    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);
        }

        /**
         * Returns the TreeNode (or null if not found) for the given key
         * starting at given root.
         */
        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;
                    else if (pl == null)
                        p = pr;
                    else if (pr == null)
                        p = pl;
                    else if ((kc != null ||
                              (kc = comparableClassFor(k)) != null) &&
                             (dir = compareComparables(kc, k, pk)) != 0)
                        p = (dir < 0) ? pl : pr;
                    else if ((q = pr.findTreeNode(h, k, kc)) != null)
                        return q;
                    else
                        p = pl;
                } while (p != null);
            }
            return null;
        }
    }

ForWaidingNode

    /* ---------------- Special Nodes -------------- */

    /**
     * A node inserted at head of bins during transfer operations.
     */
    static final class ForwardingNode<K,V> extends Node<K,V> {
        //拿到fwd節點 代表現在再擴容資料正在遷移 寫執行緒:需要參與併發擴容  讀執行緒:呼叫find方法到新表繼續查詢
        final Node<K,V>[] nextTable;
        //hash固定
        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
            //tab 一定不為空
            Node<K,V>[] tab = nextTable;
            outer: for (;;) {
                //n 表示為擴容而建立的 新表的長度
                //e 表示在擴容而建立新表使用 定址演算法 得到的 桶位頭結點
                Node<K,V> e; int n;

                //條件一:永遠不成立
                //條件二:永遠不成立
                //條件三:永遠不成立
                //條件四:在新擴容表中 重新定位 hash 對應的頭結點
                //true -> 1.在oldTable中 對應的桶位在遷移之前就是null
                //        2.擴容完成後,有其它寫執行緒,將此桶位設定為了null
                if (k == null || tab == null || (n = tab.length) == 0 ||
                    (e = tabAt(tab, (n - 1) & h)) == null)
                    return null;

                //前置條件:擴容後的表 對應hash的桶位一定不是null,e為此桶位的頭結點
                //e可能為哪些node型別?
                //1.node 型別
                //2.TreeBin 型別
                //3.FWD 型別

                for (;;) {
                    //eh 新擴容後表指定桶位的當前節點的hash
                    //ek 新擴容後表指定桶位的當前節點的key
                    int eh; K ek;
                    //條件成立:說明新擴容 後的表,當前命中桶位中的資料,即為 查詢想要資料。
                    if ((eh = e.hash) == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;

                    //eh<0
                    //1.TreeBin 型別    2.FWD型別(新擴容的表,在併發很大的情況下,可能在此方法 再次拿到FWD型別..)
                    if (eh < 0) {
                        if (e instanceof ForwardingNode) {
                            tab = ((ForwardingNode<K,V>)e).nextTable;
                            continue outer;
                        }
                        else
                            //說明此桶位 為 TreeBin 節點,使用TreeBin.find 查詢紅黑樹中相應節點。
                            return e.find(h, k);
                    }

                    //前置條件:當前桶位頭結點 並沒有命中查詢,說明此桶位是 連結串列
                    //1.將當前元素 指向連結串列的下一個元素
                    //2.判斷當前元素的下一個位置 是否為空
                    //   true->說明迭代到連結串列末尾,未找到對應的資料,返回Null
                    if ((e = e.next) == null)
                        return null;
                }
            }
        }
    }

5_小函式工具方法原始碼分解

5.1——spread(hash) 原始碼分析

    / 
	* 1100 0011 1010 0101 0001 1100 0001 1110
     * 0000 0000 0000 0000 1100 0011 1010 0101
     * 1100 0011 1010 0101 1101 1111 1011 1011
     * ---------------------------------------
     * 1100 0011 1010 0101 1101 1111 1011 1011
     * 0111 1111 1111 1111 1111 1111 1111 1111  ==HASH_BITS
     * 0100 0011 1010 0101 1101 1111 1011 1011
     *
     * 當前的hash右移16位 亦或 原來的hash  & HASH_BITS 得到正數的hash值
     * 讓高16位 也參與到運算中來
     */
    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

5.2——tabAt(tab, index) 原始碼分析

    //獲取陣列指定下標位置的元素
    @SuppressWarnings("unchecked")
    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);
    }

5.3——casTabAt(tab, index, c, v )原始碼分析

    //用cas的方式去table的指定位置設定值,設定成功返回true
    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);
    }

5.4——setTabAt(tab, index, 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);
    }

5.5——resizeStamp(int n) 原始碼分析

    /**
     * Returns the stamp bits for resizing a table of size n.
     * Must be negative when shifted left by RESIZE_STAMP_SHIFT.
     * 擴容標識戳 一致才能參與擴容
     * 16 -> 32
     * numberOfLeadingZeros(16) => 1 0000 =>27 =>0000 0000 0001 1011
     * |
     * (1 << (RESIZE_STAMP_BITS - 1)) => 1000 0000 0000 0000 => 32768
     * ---------------------------------------------------------------
     * 0000 0000 0001 1011
     * 1000 0000 0000 0000
     * 1000 0000 0001 1011
     */
    static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

5.6——tableSizeFor(int c) 原始碼分析

    /**
     * Returns a power of two table size for the given desired capacity.
     * See Hackers Delight, sec 3.2
     * 返回>=c的最小的2的次方數
     * c=28
     * n=27 => 0b 11011
     * 11011 | 01101 => 11111
     * 11111 | 00111 => 11111
     * ....
     * => 11111 + 1 =100000 = 32
     */
    private static final int tableSizeFor(int c) {
        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;
    }

相關文章