Map的實現類ConcurrentHashMap各種方法源碼解析

  1. map.put() 方法解析

        //map.put 方法
        // final V putVal(K key, V value, boolean onlyIfAbsent) {}
        public V put(K key, V value) {
            return putVal(key, value, false);//put方法實現
        }
        
        // putVal() 方法
        final V putVal(K key, V value, boolean onlyIfAbsent) {
            // key 和 value 不能爲null
            if (key == null || value == null) throw new NullPointerException();
            // spread() 方法 : return (h ^ (h >>> 16)) & HASH_BITS;
            // 高16位與低16位異或運算 例: 211221 = 211222^3 
            // 然後和 HASH_BITS 位與運算 例: ① 211221 = 211221 & 2147483647
            //                              ② 0 = 2147483648 & 2147483647  
            int hash = spread(key.hashCode());// 異或 與 運算得到的hash值
            int binCount = 0;// 初始化定義binCount
            for (Node<K,V>[] tab = table;;) {
                Node<K,V> f; int n, i, fh;
                if (tab == null || (n = tab.length) == 0)
                    tab = initTable();//執行初始化操作 初始化大小爲16
                //通過哈希計算出一個表中的位置因爲n是數組的長度,所以(n-1)&hash肯定不會出現數組越界
                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
                }
                // MOVED(-1) 值是 -1
                else if ((fh = f.hash) == MOVED)
                    tab = helpTransfer(tab, f);// 當前Map在擴容,先協助擴容,在更新值。
                else {
                    V oldVal = null;
                    // sysnchronized(線程安全)直接操作內存,對比lock性能兼容優越
                    // sysnchronized鎖住的是括號中的對象,並不是代碼塊,如果對象操作衝突就會進入等待
                    synchronized (f) {
                        if (tabAt(tab, i) == f) {
                            // fh 是 f 的 hash 值,f 是在內存中做了一個對比之後的值
                            // 當前hash存儲位置已經存在key值 執行覆蓋或者末尾添加操作
                            if (fh >= 0) {
                                binCount = 1;
                                for (Node<K,V> e = f;; ++binCount) {
                                    K ek;
                                    // 判斷是否存在重複的key 更新oldVal
                                    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;
                                    }
                                }
                            }
                            // 判斷是否是treeBin 操作 執行紅黑樹的add操作
                            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 執行紅黑樹add操作
                        if (binCount >= TREEIFY_THRESHOLD)
                            treeifyBin(tab, i);
                        if (oldVal != null)
                            return oldVal;
                        break;
                    }
                }
            }
            // 統計節點個數,如果在8個以上的話,則會調用treeifyBin方法,來嘗試轉化爲樹,或者是擴容
            // 引用地址:http://www.cnblogs.com/zerotomax/p/8687425.html
            addCount(1L, binCount);
            return null;
        }
    
        // initTable() 初始化操作
        private final Node<K,V>[] initTable() {
            Node<K,V>[] tab; int sc;
            //第一次put的時候,table還沒被初始化,進入while
            while ((tab = table) == null || tab.length == 0) {
                //sizeCtl初始值爲0,當小於0的時候表示在別的線程在初始化表或擴展表
                if ((sc = sizeCtl) < 0)
                    Thread.yield(); // lost initialization race; just spin
                //SIZECTL:表示當前對象的內存偏移量,sc表示期望值,-1表示要替換的值,設定爲-1表示要            
                //初始化表了
                else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        if ((tab = table) == null || tab.length == 0) {
                            int n = (sc > 0) ? sc : DEFAULT_CAPACITY;// 初始化操作
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = tab = nt;
                            // 位運算符 >>> : 位補零右移運算
                            // 負載係數 16 - (16 >>> 2) = 12 = 16 * 0.75
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        // 最終一定執行 sizeCtl 的賦值
                        sizeCtl = sc;
                    }
                    break;
                }
            }
            return tab;
        }

     

  2. map.get() 方法解析

        // map.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());// 得到key的hash值
            if ((tab = table) != null && (n = tab.length) > 0 &&
                // (n - 1) & h) 當前數組的長度與hash值 與運算 
                // 計算當前key 需要存儲的位置 如果不是null 
                (e = tabAt(tab, (n - 1) & h)) != null) {
                // 如果存儲的位置已經存在key
                if ((eh = e.hash) == h) {
                    // 如果key值是重複的 執行覆蓋方法
                    if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                        return e.val;
                }
             //hash值爲負值表示正在擴容,這個時候查的是ForwardingNode的find方法來定位到nextTable來
            //eh=-1,說明該節點是一個ForwardingNode,正在遷移,此時調用ForwardingNode的find方法去 
            //nextTable裏找。
            //eh=-2,說明該節點是一個TreeBin,此時調用TreeBin的find方法遍歷紅黑樹,由於紅黑樹有可能正 
            //在旋轉變色,所以find裏會有讀寫鎖。
            //eh>=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;
        }

     

  3. map.remove() 方法解析

        // map.remove() 方法
        public V remove(Object key) {
            return replaceNode(key, null, null);
        }
        
        // replaceNode() 方法
        final V replaceNode(Object key, V value, Object cv) {
            int hash = spread(key.hashCode());// 取當前key的hash值
            //循環操作 concurrentHashMap 當其他線程在執行擴容
            //操作時 可以協助處理, 處理完之後再來執行當前方法,提高擴容時copy效率
            for (Node<K,V>[] tab = table;;) {    
                Node<K,V> f; int n, i, fh;
                // 判斷數據是否爲null 或者通過計算得到的存放地址key爲null直接結束循環
                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);// 協助 執行 數據 Transfer 提高效率
                else {
                    V oldVal = null;
                    boolean validated = false;// 定義validated 爲 false
                    // synchronized 鎖括號中的對象 防止多個線程同時都操作這個對象 排隊等候
                    synchronized (f) {
                        // 判斷內存中是否存在 f 
                        if (tabAt(tab, i) == f) {
                            // fh 當前存儲位置存在數據 並且數據書鏈表存儲方式
                            if (fh >= 0) {
                                validated = true;
                                //循環查找當前鏈表數據
                                for (Node<K,V> e = f, pred = null;;) {
                                    K ek;
                                    // 判斷是否是需要remove的數據
                                    if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                         (ek != null && key.equals(ek)))) {
                                        V ev = e.val;
                                        if (cv == null || cv == ev ||
                                            (ev != null && cv.equals(ev))) {
                                            oldVal = ev;// 保留oldVal 返回
                                            if (value != null)
                                                e.val = value;
                                            // 判斷當前鏈表存儲位置 的上一個node是否是null
                                            else if (pred != null)
                                                // 移動上一個node.next = e.next
                                                // remove e
                                                pred.next = e.next;
                                            else
                                                // 設置tab的當前i位置爲e.next
                                                setTabAt(tab, i, e.next);
                                                
                                        }
                                        break;//結束循環
                                    }
                                    pred = e;//以上條件不滿足 繼續循環
                                    // 如果e.next 是null 結束循環
                                    if ((e = e.next) == null)
                                        break;
                                }
                            }
                            // 判斷是否是紅黑樹節點類型  執行紅黑樹操作方法
                            else if (f instanceof TreeBin) {
                                validated = true;
                                TreeBin<K,V> t = (TreeBin<K,V>)f;//轉換爲treeBin類型操作
                                TreeNode<K,V> r, p;
                                // 判斷樹的根節點不爲null 並且 根據hash和key得到的數據不爲null
                                if ((r = t.root) != null &&
                                    (p = r.findTreeNode(hash, key, null)) != null) {
                                    // 定義pv
                                    V pv = p.val;
                                    if (cv == null || cv == pv ||
                                        (pv != null && cv.equals(pv))) {
                                        oldVal = pv;//保留oldVal 返回使用
                                        if (value != null)// value 傳值一般都是null
                                            p.val = value;
                                        else if (t.removeTreeNode(p))//執行樹的remove操作
                                                // 從新設置數組
                                                setTabAt(tab, i, untreeify(t.first));
                                    }
                                }
                            }
                        }
                    }
                    // 剛開始以爲validated會造成死循環 經過分析這裏本身就是一個循環,在validated爲        
                    //false到這裏的時候會繼續循環,查找當前需要remove的Node,爲null的情況下直接返回
                    if (validated) {
                        if (oldVal != null) {
                            if (value == null)
                                addCount(-1L, -1);
                            return oldVal;
                        }
                        break;
                    }
                }
            }
            return null;//沒有匹配返回null
        }

     

  4. map.clear() 方法解析

        // map.clear() 方法
        public void clear() {
            long delta = 0L; // negative number of deletions
            int i = 0;
            Node<K,V>[] tab = table;
            // 循環執行數據的clear
            while (tab != null && i < tab.length) {
                int fh;
                Node<K,V> f = tabAt(tab, i);
                if (f == null)
                    ++i;
                else if ((fh = f.hash) == MOVED) {// 判斷是否有線程在執行擴容操作 
                    tab = helpTransfer(tab, f);// 執行協助
                    i = 0; // restart
                }
                else {
                    synchronized (f) {
                        if (tabAt(tab, i) == f) {
                            Node<K,V> p = (fh >= 0 ? f :
                                           (f instanceof TreeBin) ?
                                           ((TreeBin<K,V>)f).first : null);
                            while (p != null) {
                                --delta;
                                p = p.next;
                            }
                            setTabAt(tab, i++, null);
                        }
                    }
                }
            }
            if (delta != 0L)
                addCount(delta, -1);
        }

     

 

hash table雖然性能上不如ConcurrentHashMap,但並不能完全被取代,兩者的迭代器的一致性不同的,hash table的迭代器是強一致性的,而concurrenthashmap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 
下面是大白話的解釋: 
- Hashtable的任何操作都會把整個表鎖住,是阻塞的。好處是總能獲取最實時的更新,比如說線程A調用putAll寫入大量數據,期間線程B調用get,線程B就會被阻塞,直到線程A完成putAll,因此線程B肯定能獲取到線程A寫入的完整數據。壞處是所有調用都要排隊,效率較低。 
- ConcurrentHashMap 是設計爲非阻塞的。在更新時會局部鎖住某部分數據,但不會把整個表都鎖住。同步讀取操作則是完全非阻塞的。好處是在保證合理的同步前提下,效率很高。壞處 是嚴格來說讀取操作不能保證反映最近的更新。例如線程A調用putAll寫入大量數據,期間線程B調用get,則只能get到目前爲止已經順利插入的部分 數據。

選擇哪一個,是在性能與數據一致性之間權衡。ConcurrentHashMap適用於追求性能的場景,大多數線程都只做insert/delete操作,對讀取數據的一致性要求較低。
--------------------- 
原文:https://blog.csdn.net/programmer_at/article/details/79715177 
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章