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

JDK 並發容器深度解析:ConcurrentSkipListMap —— 基於跳表的線程安全有序映射

     編輯:Java 綜合教學

概覽:為什麼需要 ConcurrentSkipListMap?

  • 有序性:實現 ConcurrentNavigableMap,支持 firstKey()、floorEntry()、subMap() 等範圍操作。
  • 並發性:無鎖 CAS 算法,讀寫並發極高,無全局鎖瓶頸。
  • 性能:增刪改查平均 O(log n),迭代器弱一致性,無 ConcurrentModificationException。

適用場景:高併發緩存、路由表、限流計數器、時序數據索引、分布式鎖元數據存儲。

一、數據結構:三層節點構建的跳表

1.1 基礎節點 Node —— 底層有序單鏈表

static final class Node {
    final K key;
    volatile Object value;  // Object 區分普通值 / 刪除標記 / 頭節點
    volatile Node next;

    // 普通節點
    Node(K key, Object value, Node next) { ... }
    // 刪除標記節點:value 指向自己,key=null
    Node(Node next) { this.key=null; this.value=this; this.next=next; }
    // CAS 輔助
    boolean casValue(Object cmp, Object val) { ... }
    boolean casNext(Node cmp, Node val) { ... }
    boolean isMarker() { return value == this; }
    boolean isBaseHeader() { return value == BASE_HEADER; }
}

關鍵設計:value 為 Object 而非 V,利用特殊引用標識節點狀態:

  • 正常值 → 實際 value 對象
  • 刪除中 → this (Marker)
  • 頭節點 → BASE_HEADER 單例

1.2 索引節點 Index —— 多層索引縮小查找範圍

static class Index {
    final Node node;       // 指向底層 Node
    final Index down;      // 下一層索引(final,簡化並發)
    volatile Index right;  // 同層下一個索引

    // CAS 鏈接/解鏈接 right
    final boolean casRight(Index cmp, Index val) { ... }
    // 索引節點是否指向已刪除 Node
    final boolean indexesDeletedNode() { return node.value == null; }
    // 鏈接新索引節點
    final boolean link(Index succ, Index newSucc) { ... }
    // 解鏈接索引節點
    final boolean unlink(Index succ) { ... }
}

索引層邏輯上形成「下沉式」雙向鏈表:down 指向下層,right 指向同層後繼。

1.3 頭索引 HeadIndex —— 跟蹤最高層級

static final class HeadIndex extends Index {
    final int level;  // 索引層數,從 1 開始,底層 Node 層為 0
    HeadIndex(Node node, Index down, Index right, int level) { ... }
}

1.4 結構示意圖

ConcurrentSkipListMap 結構

(參考 Wikipedia 跳表圖示:多層索引逐層下沉,最終定位到底層有序鏈表)

二、構造器:支持 Comparator、Map、SortedMap 多種初始化

// 無參:自然順序
public ConcurrentSkipListMap() { this.comparator = null; initialize(); }
// 指定 Comparator
public ConcurrentSkipListMap(Comparator comparator) { ... }
// 從 Map 複製(自然順序)
public ConcurrentSkipListMap(Map m) { ... putAll(m); }
// 從 SortedMap 複製(保留其 Comparator)
public ConcurrentSkipListMap(SortedMap m) { ... buildFromSorted(m); }

private void initialize() {
    keySet = entrySet = values = descendingMap = null;
    randomSeed = seedGenerator.nextInt() | 0x0100; // 非零種子
    head = new HeadIndex<>(new Node<>(null, BASE_HEADER, null), null, null, 1);
}

三、核心操作:無鎖 CAS 全流程

3.1 查找前驅節點 findPredecessor(key)

從最高層 HeadIndex 開始:

  1. 同層向右遍歷 right,直到 node.key >= key 或 right == null
  2. 遇到索引節點指向已刪除 Node (node.value == null) → unlink() 刪除該索引
  3. 下沉 down 進入下一層
  4. 重複直到第 0 層(底層 Node),返回 q.node 作為前驅

作用:查找插入/刪除位置 + 順便清理失效索引。

3.2 插入/更新 doPut(key, value, onlyIfAbsent)

  1. b = findPredecessor(key) 獲取前驅
  2. 底層鏈表向後遍歷:
    - 遇到 Marker → helpDelete() 推進刪除
    - 遇到 key 相等 → CAS value 更新或 onlyIfAbsent 返回舊值
    - 找到插入點(b.key < key < n.key)
  3. CAS b.next 將新 Node 鏈入
  4. randomLevel() 隨機生成索引層數(幂次分佈,p=0.5,最大 31)
  5. level > 0 → insertIndex(z, level) 為新節點建立索引塔

3.3 索引塔構建 insertIndex(z, level)

  • level <= head.level:在現有層數內,自底向上創建 Index 鏈,調用 addIndex() 逐層 CAS right 鏈入
  • level > head.level:需增加新層 → 創建新 HeadIndex 鏈,CAS head 指針原子切換

3.4 刪除 doRemove(key, value)

  1. findPredecessor() + 底層遍歷定位目標 Node n、前驅 b、後繼 f
  2. CAS n.value → null 標記為待刪除
  3. n.appendMarker(f) 在 n 後插入 Marker 節點,阻止新節點插入其間
  4. CAS b.next → f 將 n + Marker 從鏈表摘除
  5. 失敗則調用 findNode() 重試(其他線程可能已幫助完成)
  6. 成功後 findPredecessor() 清理索引塔,tryReduceLevel() 嘗試降低最高層

四步刪除協議:value=null → appendMarker → CAS next → cleanup indexes,解決並發插入/刪除/讀取的所有競態。

3.5 讀取 doGet(key)

  1. findNode(key) 定位 Node(含清理 Marker)
  2. 讀 value,非 null 直接返回
  3. 若 null(被標記刪除)→ 重試循環

四、迭代器:弱一致性遍歷

abstract class Iter implements Iterator {
    Node lastReturned, next; V nextValue;
    Iter() { next = findFirst(); ... }  // 跳過頭節點與 Marker
    void advance() {
        next = next.next;
        while (next != null) {
            Object x = next.value;
            if (x != null && x != next) { nextValue = (V)x; break; }
            next = next.next;
        }
    }
    // ... hasNext, next, remove ...
}
  • 創建時/之後某時刻的快照視圖
  • 不拋 ConcurrentModificationException
  • 升序比降序快(降序需反向索引或雙向鏈表,CSLM 單向)

五、並發設計精華總結

技術點實現方式效果
無鎖並發CAS (Unsafe) + volatile + final無阻塞、可線性化
節點狀態編碼value 引用特殊值無額外標記字段,原子讀取
刪除協議value=null → Marker → CAS next防止 ABA、並發插入安全
索引維護懶刪除 + 遍歷時清理 + 最高層收縮索引不準確不影響正確性,僅影響查找速度
隨機層數Xorshift RNG,p=0.5期望高度 O(log n),空間 O(n)
弱一致性迭代器遍歷底層 Node 鏈表無鎖、無異常、可並發修改

六、性能特徵與選型建議

  • 優勢:高併發寫入下有序操作延遲穩定、無全局鎖、內存效率高於紅黑樹(無顏色位、無父指針)
  • 劣勢:size() 需全遍歷(非 O(1))、範圍查詢需遍歷底層鏈表、降序迭代慢
  • 對比:
    • vs ConcurrentHashMap:無序、吞吐更高、size() O(1)
    • vs Collections.synchronizedSortedMap(TreeMap):粗粒度鎖、擴展性差
    • vs TreeMap:非線程安全

最佳實踐:需要「高併發 + 有序 + 範圍查詢」時首選;純緩存/計數用 ConcurrentHashMap;單線程/低併發用 TreeMap。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved