Appearance
ConcurrentSkipListMap源码
看在前面
概述与选型
ConcurrentSkipListMap 是线程安全的有序 Map,基于跳表(SkipList)实现:
- 与 TreeMap 对比:TreeMap 非线程安全,基于红黑树。非多线程场景应优先用 TreeMap;并发较低可用
Collections.synchronizedSortedMap包装 TreeMap;高并发且需要有序 key 时用 ConcurrentSkipListMap; - 与 ConcurrentHashMap 对比:4 线程 1.6 万数据下 ConcurrentHashMap 的存取速度约为其 4 倍,但 ConcurrentSkipListMap 有两点优势:
- key 有序;
- 支持更高并发——存取时间为 O(log N),几乎与线程数无关。线程越多优势越明显;
- 注意:
size()因多线程并发修改,需要遍历链表统计,是 O(n) 量级的开销操作,不应频繁调用。
跳表结构
跳表分多层,每层是一条有序索引。上层是下层的子集,第 1 层包含全部数据。层越高跳跃越大、数据越少。查找从表头开始,自上而下、从左往右。
以 7,14,21,32,37,71,85 为例:单链表找 32 需要 4 步,跳表只需 2 步(顶层直接跳过 14、21)。
类结构与并发字段
- 继承 AbstractMap;
- 内部类
Index对应跳表索引,HeadIndex继承 Index,其实例 head 为表头; - 通过 volatile/final 字段配合 CAS 实现并发安全:
java
static final class Node<K,V> {
final K key; // final
volatile V value; // volatile
volatile Node<K,V> next;
}
static final class Index<K,V> {
final Node<K,V> node; // final
final Index<K,V> down; // final
volatile Index<K,V> right;
}put → doPut(JDK1.7 源码)
主干是单线程插入逻辑,其余都是并发冲突处理:
findPredecessor(key)找前继节点 b,n = b.next 为待插入位置的后继;- 并发防护:若
n != b.next、n.value == null(节点被删)、b.value == null(b 被删),说明读到了不一致状态。break 回外层 for 重试; - key 比较结果处理:
- c > 0:b = n,n = f,继续右移;
- c == 0:onlyIfAbsent 为真或
n.casValue(v, value)成功则返回旧值; - c < 0:新建节点
z(key, value, n),b.casNext(n, z)插入底层链表;CAS 失败则 break 重试;
randomLevel()随机选层。level > 0 时调用insertIndex(z, level)从第 1 层到 level 层逐层插入索引,必要时升高跳表。
remove → doRemove
- 同样先找前继 b、目标节点 n、后继 f。并发检查与 put 一致(读取不一致或节点已被删则重试);
- c < 0 说明 key 不存在,返回 null;c > 0 继续右移;c == 0 时:
- 若指定了期望 value 且不等于当前 v,返回 null(条件删除失败);
n.casValue(v, null)把 value 置空,标记删除;n.appendMarker(f)追加 marker 节点,b.casNext(n, f)把 n 从链表摘除;失败则通过findNode(key)重试;
- 成功后
findPredecessor(key)顺带清理各层索引;若head.right == null则tryReduceLevel()降低跳表层数。
删除采用 value 置 null + marker 的惰性删除方式:其他线程遇到已删节点时会 helpDelete(b, f) 协助完成摘除。
get → doGet → findNode
findNode 主干:
- findPredecessor 找 b,n = b.next;
- n == null 返回 null(key 不存在);
- compareTo:c == 0 返回 n;c < 0 返回 null;c > 0 则 b = n、n = f 继续右移。
doGet 循环调用 findNode,取到 n.value 非 null 即返回。get 全程无锁,依赖 volatile 读与"删除先置 null"的约定保证一致性。
使用示例
java
Map<String, String> map = new ConcurrentSkipListMap<>();
// 线程 a、b 各循环 6 次:以 "线程名+序号" 为 key、"0" 为 value put,
// 并各自用 Iterator 遍历打印全部元素:
// ConcurrentSkipListMap:程序正常运行,输出按 key 有序
// 换成 TreeMap:抛出 ConcurrentModificationException一句话总结:需要并发 + 按 key 有序(有序遍历、范围查询)时选 ConcurrentSkipListMap;只要并发不需要有序,选 ConcurrentHashMap 更快。
内容来源:原文 · 2026-10 整理回填。