jdk1.8源碼分析之ConcurrentHashMap分析

  • ConcurrentHashMap 是java提供的一個線程安全的鍵值對集合,1.7採用分段鎖的模式,1.8採用CAS+synchronized 保證安全。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {
    
       private static final int DEFAULT_CAPACITY = 16;
        // 併發級別 
        private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
        // 存儲數組
       transient volatile Node<K,V>[] table;
        // 控制多線程數組的初始化操作。 volite 保證了對其讀寫操作的可見性,原子性。禁止重排序
        // 初始化完成後,則控制調整大小的閾值。
        private transient volatile int sizeCtl;
    
    // 存儲方法
    public V put(K key, V value) {
        return putVal(key, value, false);
    }

       final V putVal(K key, V value, boolean onlyIfAbsent) {
        // key value 都不能爲null.這個和HashMap不一樣。
        if (key == null || value == null) throw new NullPointerException();
        // 對key 的哈希碼做一些處理。
        int hash = spread(key.hashCode());
        int binCount = 0;
        // 循環嘗試插入節點直到插入成功。
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // 初始化數組爲null或長度爲0 ,則調用初始化方法進行。
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // 獲取i位置上的元素,爲null則繼續。
            else if ((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值爲-1 則說明在擴容遷移中
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //對已存在的節點鏈表的首節點進行加鎖,進行操作。這裏已經產生hash碰撞
                synchronized (f) {
                    // 判斷i位置的節點是否等於f,等於說明其他線程沒有刪除該節點操作。
                    // 這裏使用了雙重檢查,防止操作時該節點被刪除或者移動了位置等。
                    // 如果不等於,則f被刪除或者移動到了其他位置,則循環繼續嘗試添加。
                    if (tabAt(tab, i) == f) {
                        // 當前節點的hash值大於等於0 
                        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;
                            }
                        }
                    }
                }
                // binCount  大於0則代表是hash碰撞產生
                if (binCount != 0) {
                    //大於等於8則將該節點對應的鏈表轉化爲紅黑樹存儲
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    //如果不是覆蓋,則返回null.覆蓋原來節點的值則返回原來存儲的值。
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //這裏會設置BaseCount的值,用來計算size大小。bincount代表插入位置鏈表的長度或者插入節點爲樹節點
        // 1L 代表basecount加1.
        addCount(1L, binCount);
        return null;
    }

    
    // 初始化table 方法
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
                // 如果sizeCtl 小於0,說明應該有其他線程在操作,則當前線程調用yield 方法
            //暫停當前線程執行,當前線程由運行狀態變爲可執行狀態
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            // 否則嘗試使用CAS設置sizeCtl 的值爲-1   SIZECTL 標識sizeCtl  變量相對於this對象的偏移量,sc是原來的值0,比較當前對象位置上sizeCtl   的存儲值是否等於0,等於則設置sizeCtl  值爲-1.即先比較後交換。比較原來的值和當前次對象位置上存儲的值是否相等,相等代表沒有其他線程修改過,則可以安全的更新值。CAS是比較常用的搭配volite 關鍵字實現原子性對變量的更新操作。
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        // 默認長度16,構建長度16的node節點數組。
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                //初始化完成,退出循環。
                break;
            }
        }
        // 返回tab
        return tab;
    }
        
    // 獲取數組i位置的元素。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);
    }

    // 這裏我們來看下如何計算size 的大小的。
     private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        // counterCells 用來記錄當競爭激烈時,增加的size大小。
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            //counterCells 不爲空或者CAS設置baseCount 失敗。baseCount  記錄競爭不激烈的情況下size的大小。統計size時,baseCount加上counterCells數組記錄的長度的和
            CounterCell a; long v; int m;
            boolean uncontended = true;
            // counterCells 等於null 或者長度小於1  或者當前線程的探針值與當前長度減一 位置上的元素爲null,不爲null則CAS 嘗試將該元素值更新,更新失敗則進入。這是避免counterCells數組在競爭激烈情況下長度過長,同時同一個線程操作的計數可以放在同一個位置進行統計。
            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(x, uncontended);
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
        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);
                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();
            }
        }
    }

    /**
     * Initialize Thread fields for the current thread.  Called only
     * when Thread.threadLocalRandomProbe is zero, indicating that a
     * thread local seed value needs to be generated. Note that even
     * though the initialization is purely thread-local, we need to
     * rely on (static) atomic generators to initialize the values.
    *初始化當前線程的線程字段。僅在Thread.threadLocalRandomProbe爲零時調用,表示需要生成線        
    * 程本地種子值。請注意,即使*初始化純粹是線程局部的,我們也需要依靠(靜態)原子生成器來初始化值
     */
    static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
    }

    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 數組不等於空且長度大於0,則說明不是第一次,不需要初始化該數組。
            if ((as = counterCells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    // 該線程是第一次進入該數組進行計數統計
                    if (cellsBusy == 0) { 
                        // 嘗試創建新的單元格即 CounterCell 當cellsBusy 等於0
                  // Try to attach new Cell
                        CounterCell r = new CounterCell(x); // Optimistic create
                        // cellsBusy  等於0且CAS更新值爲1 成功
                        if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                            boolean created = false;
                            try {               // Recheck under lock
                                CounterCell[] rs; int m, j;
                                // j 當前線程應該存放到位置 再次檢查rs數組,雙重檢查機制
                                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
                        }
                    }
                    collide = false;
                }
                // CAS  設置失敗 
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // CAS 嘗試更新a的屬性值,a就是找到的元素對象
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;
                // counterCells 被改變或者其原來的長度大於等於處理器個數
                else if (counterCells != as || n >= NCPU)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 &&
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                    // cellsBusy 等於0且嘗試更新值爲1 成功
                    try {    
                              // counterCells  沒有改變
                        if (counterCells == as) {// Expand table unless stale
                            CounterCell[] rs = new CounterCell[n << 1];
                            //構建長度爲原來2倍的新數組並將原來數組copy到新數組
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            // counterCells  變更爲新數組,長度爲原來的2倍
                            counterCells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = ThreadLocalRandom.advanceProbe(h);
            }
            else if (cellsBusy == 0 && counterCells == as &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                boolean init = false;
                try {   
                    // 初始化 counterCells  默認長度爲2 
                       // Initialize table
                    if (counterCells == as) {
                        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
        }
    }

    // 這是一個靜態常量內部類。只有一個volatile 修飾的value屬性,保證value的CAS更新操作具有可見性。
   @sun.misc.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }
}
  • 通過以上的源碼分析:我們可以看到jdk1.8 實現的ConcurrentHashMap 底層數據結構是數組+鏈表+紅黑樹,通過CAS和synchronized 來控制多線程下併發訪問。通過對鏈表或者紅黑樹首節點的加鎖提高了併發度。
  • 通過basecount+CounterCell[] 數組來計算size。basecount在無競爭的情況下就足夠使用了。
  • 如果我們通過CAS 設置basecount 的值失敗,則會嘗試使用CounterCell 數組,CounterCell數組的每個位置上的元素記錄了一個線程更改增加的元素個數。位置的計算由線程探針位移與數組長度-1就是相當於取模對CounterCell數組長度 。
  • CounterCell 數組初始化長度爲2,在嘗試插入或者更新失敗後,會嘗試擴展數組即擴展爲原來的2倍。
  • 通過CounterCell 數組的設計可以避免線程記錄長度時產生競爭,當然這個相當於hash的計算還是可能產生碰撞的,但是通過cas競爭時會導致更新失敗,則繼續不斷嘗試則可能最大程度避免計數失敗。
  • CounterCell  + basecount的計數方式,即適用於競爭不大的情況,也適應了多線程競爭激烈情況下的併發度考慮。優化了多線程操作時的計數。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章