首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

ConcurrentHashMap JDK 1.8 核心改進深度解析:從分段鎖到 CAS + synchronized + 紅黑樹

     編輯:Java 綜合教學

JDK 1.7 回顧:Segment 分段鎖架構

  • 數組 Segment[] segments,每個 Segment 繼承 ReentrantLock,內含 HashEntry<K,V>[] table。
  • 併發度 = Segment 數量(默認 16),擴容時需鎖定整個 Segment。
  • 查詢無鎖(volatile 保證可見性),寫操作鎖 Segment。

JDK 1.8 兩大核心改進

改進一:摒棄 Segment,採用 Node[] + CAS + synchronized 細粒度鎖

  • 直接用 transient volatile Node<K,V>[] table 存儲數據。
  • putVal 邏輯:
    1. 計算索引 i = (n-1) & hash。
    2. 若 tab[i] == null,用 casTabAt (Unsafe CAS) 無鎖插入首節點。
    3. 若首節點 hash == MOVED,協助擴容。
    4. 否則 synchronized 鎖住首節點,遍歷鏈表/紅黑樹更新或插入。
  • 鎖粒度從 Segment 降級到數組索引(bin),併發度大幅提升。

改進二:鏈表轉紅黑樹,最壞查詢 O(n) → O(log n)

  • 鏈表長度 ≥ TREEIFY_THRESHOLD (默認 8) 且數組長度 ≥ MIN_TREEIFY_CAPACITY (64) 時,treeifyBin 轉為紅黑樹(TreeBin 包裝 TreeNode)。
  • 樹節點數 ≤ UNTREEIFY_THRESHOLD (6) 時退化為鏈表。
  • 紅黑樹查找/插入/刪除均為 O(log n),極端哈希衝突下性能不再退化。

關鍵源碼片段:putVal 核心邏輯

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    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<>(hash, key, value, null)))
                break; // 無鎖插入成功
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 協助擴容
        else {
            V oldVal = null;
            synchronized (f) { // 鎖住 bin 首節點
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) { // 鏈表
                        // 遍歷更新或尾插
                    }
                    else if (f instanceof TreeBin) { // 紅黑樹
                        // putTreeVal
                    }
                }
            }
            if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i);
        }
    }
    addCount(1L, binCount);
    return null;
}

性能實測對比(4 線程各寫 10 萬,再雙線程讀 40 萬)

JDK 版本put 耗時 (ms)get 耗時 (ms)
1.7~ 185~ 42
1.8~ 95~ 28
提升~48%~33%

1.8 在高併發寫入場景優勢明顯,得益於更細鎖粒度與無鎖 CAS 插入。

附錄:LongAdder 思想的 CounterCell 優化 size()

  • 1.7 size() 需遍歷所有 Segment 加鎖求和,慢且阻塞。
  • 1.8 引入 CounterCell[] 分段計數器,addCount 以 CAS/細鎖更新,size() 無鎖求和,性能數量級提升。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved