concurrentskiplistmap 的 size() 方法返回近似值而非精确计数,因其无锁跳表结构需遍历统计且遍历中节点可能被并发修改,导致结果弱一致、不可预测。

ConcurrentSkipListMap 的 size() 方法在并发环境下返回的是近似值,不是精确计数,这是由其底层跳表结构和无锁设计决定的。
size() 需要遍历才能统计
不同于 HashMap 或 ConcurrentHashMap(内部维护计数器),ConcurrentSkipListMap 没有原子更新的全局 size 字段。每次调用 size() 都会从头开始遍历整个跳表,逐个统计有效节点。这个过程本身耗时,且不加锁。
- 遍历期间其他线程可能正在插入、删除节点,导致部分节点被重复计数或遗漏
- 遍历路径依赖当前跳表的索引层级状态,而索引节点也在并发更新中动态调整
- 逻辑已删除节点(值为 null)会被跳过,但它们的“残留结构”可能干扰遍历完整性
弱一致性体现为结果不可预测
所谓弱一致性,是指 size() 返回的不是某一时刻的快照,而是遍历窗口内“看到什么就算什么”的中间态:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 同一时刻连续调用两次 size(),可能得到不同结果(比如 102、98、105)
- 即使没有写操作,仅因遍历路径差异(如升序 vs 降序迭代器底层路径不同),结果也可能略有出入
- 高并发下误差可能达百分之几,尤其在频繁增删场景中
批量操作加剧非原子性问题
像 putAll、clear 等方法本身就不保证原子性,它们在执行过程中会分批修改跳表。此时调用 size() 很容易落在“半完成”状态:
- putAll 正在插入 1000 个元素,size() 可能返回 320、670、999 等任意中间值
- clear() 并非真正清空物理节点,而是标记删除,size() 统计时可能漏掉尚未被完全清理的节点
- subMap.clear() 实际清空的是整个原 map,但 size() 无法反映这种间接影响的即时性
替代方案需按语义选择
如果业务逻辑依赖精确数量,不能靠 size() 判断,应主动管理计数:
- 用 AtomicLong 单独维护总数,每次 put/remove 同步增减(注意与 map 数据的一致性边界)
- 需要强一致视图时,改用 TreeMap + synchronized 块,牺牲并发吞吐换取语义确定性
- 监控类场景(如日志统计)可接受误差,直接用 size() 即可,无需额外开销
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










