collections.synchronizedsortedmap的submap等视图共享外层map的同一把互斥锁,所有操作串行化执行,不创建新锁或数据副本,边界检查每次调用时动态进行,性能瓶颈明显。

Collections.synchronizedSortedMap 返回的同步包装器,对 subMap 等范围视图的锁机制是**继承外层 Map 的互斥锁**,而非为子视图单独加锁。
锁对象统一,所有操作串行化
该包装器内部持有一个独占锁对象(通常是传入的 map 本身或显式指定的 mutex),所有公开方法(包括 subMap()、headMap()、tailMap() 返回的视图)都通过 synchronized(mutex) 保证线程安全。这意味着:
- 调用
subMap(from, to)本身会持锁,返回的子视图对象也不持有新锁,其所有方法(如get()、put()、keySet().iterator())仍需同步在同一个 mutex 上 - 即使两个线程分别操作原始 map 和它的某个 subMap,也会因竞争同一把锁而阻塞,不会出现并发修改异常,但可能造成不必要的串行等待
- 子视图的结构一致性由外层锁保障:比如在获取 subMap 后,另一线程修改了原 map 中影响范围边界的键,subMap 的后续操作仍能反映最新状态(因为每次调用都重新校验边界并委托给底层 map)
subMap 返回的是同步代理,不是独立副本
返回的 SortedMap 实例是包装器内部定义的私有类(如 SynchronizedSortedMap.SynchronizedSubMap),它不复制数据**,所有操作最终转发给底层原始 map,并包裹在 synchronized 块中。例如:
-
subMap.get(k)→ 检查 k 是否在 [from, to) 范围内 → 若合法,则调用底层 map 的get(k),全程在 mutex 下执行 -
subMap.keySet().iterator()返回的迭代器也是同步的,每次next()都受同一把锁保护,避免遍历时被其他线程修改结构 - 注意:
subMap的边界检查发生在每次方法调用时,不是创建时快照 —— 所以如果原 map 在 subMap 创建后插入了新键,只要该键落在范围内,就会出现在 subMap 中
实际使用中的关键限制
这种设计虽简单安全,但存在明显性能瓶颈和语义陷阱:
- 粒度粗:一次 subMap 的遍历会持续占用整个 map 的锁,期间其他线程无法读写任何键,哪怕只操作完全无关的区间
-
无法嵌套调用:比如在 synchronized 代码块内调用
subMap.subMap(...)不会死锁(因为锁可重入),但逻辑上仍是单锁串行,无并发增益 -
不适用于高并发读场景:即使多个线程只读不同 subMap,仍互相阻塞;此时应考虑
ConcurrentSkipListMap或手动分段加锁 -
注意迭代器弱一致性:虽然不会抛
ConcurrentModificationException,但迭代过程中其他线程的修改可能被部分可见(取决于执行时机),不保证“快照”语义
替代方案建议
若需要更细粒度或更高吞吐的范围操作:
- 优先使用
ConcurrentSkipListMap:它原生支持并发的subMap()、headMap()等,各视图操作独立,无全局锁 - 若必须用 synchronized 包装器,且读多写少,可考虑将 subMap 结果转为不可变副本(如
new TreeMap(subMap))供只读使用,释放锁 - 对特定范围高频访问的场景,可自行实现基于
ReentrantLock分段的 SortedMap 包装器,按 key 区间划分锁粒度
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











