ConcurrentSkipListMap 是线程安全的有序 Map,基于跳表实现 log(n) 并发读写并保持键序,但迭代器弱一致、不允 null 键、内存开销高,适用于高并发+范围查询场景。

ConcurrentSkipListMap 是线程安全的有序 Map,但不是万能替代品
它底层基于跳表(skip list),支持 log(n) 时间复杂度的并发读写,且天然保持键的自然顺序或自定义顺序。但它不保证强一致性——比如迭代器是弱一致的(weakly consistent),不会抛 ConcurrentModificationException,但可能看不到最新写入、也可能看到重复元素。如果你需要实时精确的快照视图,别用它遍历;真要全量读,考虑先 toArray() 或转成不可变副本。
构造时必须注意 Comparator 的线程安全性与 null 处理
ConcurrentSkipListMap 允许传入 Comparator,但该比较器本身不能在内部修改共享状态,否则会引发不可预测行为。更关键的是:它**不允许键为 null**,无论是否自定义 Comparator,一旦插入 null 键,立刻抛 NullPointerException。常见踩坑点是把未判空的 DTO 字段直接当 key 用:
Mapmap = new ConcurrentSkipListMap<>(); map.put(user.getName(), user.getScore()); // user.getName() == null → NPE
建议在 put 前统一校验,或封装一层防御性 wrapper。
和 ConcurrentHashMap + 排序逻辑相比,何时选它?
它适合「高并发 + 需要范围查询 + 顺序敏感」的场景,比如实时排行榜按分数查 topN、时间窗口内统计(key 是 Instant)、分布式任务调度中的优先队列模拟。但若只是简单计数、高频单 key 更新、或顺序无关,ConcurrentHashMap 加上外部排序更轻量。注意:ConcurrentSkipListMap 的内存开销比 ConcurrentHashMap 高约 2–3 倍,因为每层索引都要存额外指针。
立即学习“Java免费学习笔记(深入)”;
- 要
subMap(fromKey, toKey)、headMap(toKey)、tailMap(fromKey)→ 选它 - 只做
get(key)/put(key, value),且 key 无序 → 用ConcurrentHashMap - 需要严格 FIFO 或 LRU 行为 → 它不支持,得换
ConcurrentLinkedQueue或自己加锁维护
迭代器不阻塞,但结果未必反映“某一时刻”的状态
它的 entrySet().iterator() 是弱一致的:不阻塞写操作,也不保证看到所有已提交变更。例如,在遍历时另一个线程连续 put 两个新 entry,你可能只看到其中一个,或都看不到,甚至看到旧值。这不是 bug,是设计取舍。如果业务逻辑依赖“遍历时看到全部当前数据”,必须加同步或改用其他方案,比如定期生成快照:
ConcurrentSkipListMaplogMap = new ConcurrentSkipListMap<>(); // 危险:下面的 for 循环结果不稳定 for (Map.Entry e : logMap.entrySet()) { process(e); } // 安全:先复制再处理(注意内存与一致性权衡) List > snapshot = new ArrayList<>(logMap.entrySet());
跳表结构决定了它没法像 B+ 树那样提供可串行化的遍历语义——这点容易被忽略,尤其在审计、对账类需求里。










