硬核講解JDK1.8的ConcurrentHashMap爲何放棄分段鎖改用CAS + synchronized

jdk1.7分段鎖的實現

和hashmap一樣,在jdk1.7中ConcurrentHashMap的底層數據結構是數組加鏈表。和hashmap不同的是ConcurrentHashMap中存放的數據是一段段的,即由多個Segment(段)組成的。每個Segment中都有着類似於數組加鏈表的結構。

關於Segment

ConcurrentHashMap有3個參數:

  • initialCapacity:初始總容量,默認16
  • loadFactor:加載因子,默認0.75
  • concurrencyLevel:併發級別,默認16

其中併發級別控制了Segment的個數,在一個ConcurrentHashMap創建後Segment的個數是不能變的,擴容過程過改變的是每個Segment的大小

關於分段鎖

段Segment繼承了重入鎖ReentrantLock,有了鎖的功能,每個鎖控制的是一段,當每個Segment越來越大時,鎖的粒度就變得有些大了。

分段鎖的優勢在於保證在操作不同段 map 的時候可以併發執行,操作同段 map 的時候,進行鎖的競爭和等待。這相對於直接對整個map同步synchronized是有優勢的。

缺點在於分成很多段時會比較浪費內存空間(不連續,碎片化); 操作map時競爭同一個分段鎖的概率非常小時,分段鎖反而會造成更新等操作的長時間等待; 當某個段很大時,分段鎖的性能會下降。
jdk1.8的map實現

和hashmap一樣,jdk 1.8中ConcurrentHashmap採用的底層數據結構爲數組+鏈表+紅黑樹的形式。數組可以擴容,鏈表可以轉化爲紅黑樹。

什麼時候擴容?

  • 當前容量超過閾值
  • 當鏈表中元素個數超過默認設定(8個),當數組的大小還未超過64的時候,此時進行數組的擴容,如果超過則將鏈表轉化成紅黑樹

什麼時候鏈表轉化爲紅黑樹?

  • 數組大小已經超過64並且鏈表中的元素個數超過默認設定(8個)時,將鏈表轉化爲紅黑樹

ConcurrentHashMap的put操作代碼如下:

/** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        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;
            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, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            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, 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;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

騰訊雲大佬的上面代碼的講解
在這裏插入圖片描述

把數組中的每個元素看成一個桶。可以看到大部分都是CAS操作,加鎖的部分是對桶的頭節點進行加鎖鎖粒度很小

爲什麼不用ReentrantLock而用synchronized ?

減少內存開銷:如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步。
內部優化:synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。

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