1. 程式人生 > >【java基礎之ConcurrentHashMap原始碼分析】

【java基礎之ConcurrentHashMap原始碼分析】

概述:

ConcurrentHashMap這個類在java.lang.current包中,這個包中的類都是執行緒安全的。ConcurrentHashMap底層儲存資料的結構與1.8的HashMap是一樣的,都是陣列+連結串列(或紅黑樹)的結構。在日常的開發中,我們最長用到的鍵值對儲存結構的是HashMap,但是我們知道,這個類是非執行緒安全的,在高併發的場景下,在進行put操作的時候有可能進入死迴圈從而使伺服器的cpu使用率達到100%;sun公司因此也給出了與之對應的執行緒安全的類。在jdk1.5以前,使用的是HashTable,這個類為了保證執行緒安全,在每個類中都添加了synchronized關鍵字,而想而知在高併發的情景下相率是非常低下的。為了解決HashTable效率低下的問題,官網在jdk1.5後推出了ConcurrentHashMap來替代飽受詬病的HashTable。jdk1.5後ConcurrentHashMap使用了分段鎖的技術。在整個陣列中被分為多個segment,每次get,put,remove操作時就鎖住目標元素所在的segment中,因此segment與segment之前是可以併發操作的,上述就是jdk1.5後實現執行緒安全的大致思想。但是,從描述中可以看出一個問題,就是如果出現比較機端的情況,所有的資料都集中在一個segment中的話,在併發的情況下相當於鎖住了全表,這種情況下其實是和HashTable的效率出不多的,但總體來說相較於HashTable,效率還是有了很大的提升。jdk1.8後,ConcurrentHashMap摒棄了segment的思想,轉而使用cas+synchronized組合的方式來實現併發下的執行緒安全的,這種實現方式比1.5的效率又有了比較大的提升。

定義變數

     1、ziseCtr:該變數主要是用來控制陣列的初始化和擴容的,預設值為0,可以概括一下4種狀態:

    1.1、sizeCtr=0:預設值;

    1.2、sizeCtr=-1:表示Map正在初始化中;

    1.3、sizeCtr=-N:表示正在有N-1個執行緒進行擴容操作;

    1.4、sizeCtr>0: 未初始化則表示初始化Map的大小,已初始化則表示下次進行擴容操作的閾值;

  2、table:用於儲存連結串列或紅黑數的陣列,初始值為null,在第一次進行put操作的時候進行初始化,預設值為16;

  3、nextTable:在擴容時新生成的陣列,其大小為當前table的2倍,用於存放table轉移過來的值;

  4、Node:該類儲存資料的核心,以key-value形式來儲存;

  5、ForwardingNode:這是一個特殊Node節點,僅在進行擴容時用作佔位符,表示當前位置已被移動或者為null,該node節點的hash值為-1;

原始碼部分

 

    /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <p>The value can be retrieved by calling the {@code get} method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}
     * @throws NullPointerException if the specified key or value is null
     */
    public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
       //key和value不能為空,為空則直接返回異常
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        //這個過程是非阻塞的,放入失敗會一直迴圈嘗試,直至成功
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh; K fk; V fv;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else if (onlyIfAbsent && fh == hash &&  // check first node
                     ((fk = f.key) == key || fk != null && key.equals(fk)) &&
                     (fv = f.val) != null)
                return fv;
            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;
                                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);
                                    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;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }