













想了解 ConcurrentSkipListMap 实现并发安全的底层原理,我会从它的核心数据结构、并发控制机制、核心操作逻辑三个维度,用通俗易懂的方式讲解,同时对比它和 ConcurrentHashMap 的差异,帮你理解它的设计思路和适用场景。
ConcurrentSkipListMap 是 Java 中有序且高并发的 Map 实现类,基于跳表(SkipList) 实现,核心特点是:
TreeMap 一样,按键自然排序或自定义排序;Collections.synchronizedSortedMap(new TreeMap<>()) 高效得多。要理解它的并发实现,首先要懂跳表的基础结构 —— 跳表是 “分层的链表”,可以看作 “有序链表的优化版”,能把查找、插入、删除的时间复杂度降到 O(log n)。
层级4:head → ----------------------------> 60 → null
层级3:head → --------> 30 → ------------> 60 → null
层级2:head → --------> 30 → 40 → 50 → 60 → null
层级1:head → 10 → 20 → 30 → 40 → 50 → 60 → null (原始有序链表)
相比红黑树(TreeMap/ConcurrentHashMap 桶内结构),跳表的节点插入 / 删除逻辑更简单,更容易实现无锁的并发控制 —— 红黑树的旋转操作在并发下很难保证原子性,而跳表只需修改节点的指针,配合 CAS 就能实现。
ConcurrentSkipListMap 完全基于无锁化设计(CAS + 自旋) 实现并发安全,没有使用 synchronized 或 Lock 锁,核心思路是:对单个节点的指针操作通过 CAS 保证原子性,通过自旋处理竞争,通过 “标记删除” 解决并发删除的冲突。
下面以插入操作为例,拆解并发安全的实现逻辑(删除 / 查找逻辑类似):
// 简化版插入逻辑(核心思想)
private V doPut(K key, V value, boolean onlyIfAbsent) {
// 1. 查找插入位置,获取前驱节点(prev)和后继节点(next)
Node<K,V> prev, next;
int hash = key.hashCode();
for (;;) { // 自旋,直到插入成功
// 查找前驱节点和后继节点
if (findPredecessor(key, hash, out: prev, next)) {
// 键已存在:CAS 更新值
if (next.casValue(next.value, value)) {
return next.value;
}
} else {
// 键不存在:创建新节点
Node<K,V> newNode = new Node<>(hash, key, value);
// CAS 替换前驱节点的 next 指针,插入新节点
if (prev.casNext(next, newNode)) {
// 3. 随机提升新节点到上层(跳表的层级随机化)
promote(newNode);
return null;
}
}
// CAS 失败(其他线程修改了 prev 的 next 指针),自旋重试
}
}
CAS 原子操作:
casNext()(原子更新节点的 next 指针)、casValue()(原子更新节点的值);自旋重试:
标记删除(解决并发删除冲突):
value 为 null 或 deleted);层级提升的并发安全:
get()、迭代)完全无锁,直接遍历跳表;volatile 修饰,保证多线程间的可见性(一个线程修改后,其他线程能立即看到)。| 维度 | ConcurrentSkipListMap | ConcurrentHashMap(JDK8) |
|---|---|---|
| 数据结构 | 跳表(SkipList) | 数组 + 链表 / 红黑树 |
| 有序性 | 天然有序(按键排序) | 无序(桶内有序,整体无序) |
| 并发实现 | 无锁(CAS + 自旋) | 轻量级锁(CAS + synchronized 锁桶节点) |
| 时间复杂度 | 所有操作 O (log n) | 平均 O (1),最坏 O (log n)(桶内红黑树) |
| 适用场景 | 有序并发场景(如排行榜、区间查询) | 无序高并发读写场景(如缓存、通用存储) |
| null 支持 | 键 / 值都不允许 null | 键 / 值都不允许 null |
import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;
public class ConcurrentSkipListMapTest {
private static final int THREAD_COUNT = 10;
private static final int OP_COUNT = 1000;
public static void main(String[] args) throws InterruptedException {
// 有序且并发安全的 Map
Map<Integer, String> skipListMap = new ConcurrentSkipListMap<>();
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
// 多线程写入
for (int i = 0; i < THREAD_COUNT; i++) {
int threadId = i;
new Thread(() -> {
for (int j = threadId * OP_COUNT; j < (threadId + 1) * OP_COUNT; j++) {
skipListMap.put(j, "value-" + j);
}
latch.countDown();
}).start();
}
latch.await();
long end = System.currentTimeMillis();
// 验证有序性和数据完整性
System.out.println("最终大小:" + skipListMap.size()); // 输出 10000
System.out.println("第一个键:" + skipListMap.firstKey()); // 输出 0
System.out.println("最后一个键:" + skipListMap.lastKey()); // 输出 9999
System.out.println("耗时:" + (end - start) + "ms");
}
}
输出结果(示例):
最终大小:10000
第一个键:0
最后一个键:9999
耗时:85ms
ConcurrentSkipListMap 的核心优势。ConcurrentSkipListMap 基于跳表和无锁化设计(CAS + 自旋) 实现并发安全,通过 CAS 保证节点指针 / 值的原子更新,自旋处理竞争,标记删除解决并发删除冲突;ConcurrentSkipListMap,无序高并发选 ConcurrentHashMap,单线程有序选 TreeMap。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。