collections.synchronizedsortedmap 是轻量级线程安全包装器,为 sortedmap 单点操作自动加锁,但遍历、检查后写入等复合操作需手动同步,高并发写场景应改用 concurrentskiplistmap。

Collections.synchronizedSortedMap 是 Java 提供的轻量级线程安全包装工具,它不改变原有 SortedMap(如 TreeMap)结构,而是为其所有单点操作(put/get/remove/containsKey 等)自动加锁,让并发读写不会破坏内部排序或引发 ConcurrentModificationException。但它不是“全自动安全”,关键在于清楚它保护什么、不保护什么。
它能直接保证安全的操作
以下操作调用即安全,无需额外同步:
-
单次读写操作:如
map.get("key")、map.put(100, "value")、map.remove(200)、map.containsKey("x") -
一次性结构变更:如
map.clear()或启动时批量初始化(map.putAll(configMap)) -
获取视图对象本身:如
map.keySet()、map.entrySet()、map.subMap(from, to)这些调用本身是线程安全的,返回的是未同步的视图,但获取动作没问题
必须手动加锁的典型场景
这些操作看似简单,但涉及多个步骤或隐式迭代,synchronizedSortedMap 不覆盖其原子性:
-
检查后写入(check-then-act):例如“若 key 不存在才插入”,不能拆成两步:
❌if (!map.containsKey(k)) map.put(k, v);
✅ 正确写法:synchronized (map) { if (!map.containsKey(k)) map.put(k, v); } -
遍历整个 map 或其子视图:无论用 for-each、iterator 还是 stream,只要访问
entrySet()、keySet()或subMap().keySet(),都必须以原始包装后的 map 实例为锁对象:
✅synchronized (map) { for (var e : map.entrySet()) { ... } }
✅SortedMap sub = map.subMap(10, 50); synchronized (map) { for (var k : sub.keySet()) { ... } } -
遍历中修改 map:即使加了锁,也不建议在遍历循环内调用
map.remove()或map.put(),容易逻辑错乱;应先收集待操作 key,再统一处理
常见误用与风险提示
这些做法看似合理,实则破坏线程安全性:
-
对 keySet() 或 entrySet() 对象加锁:如
synchronized (map.keySet()) { ... }—— 锁对象错误,完全无效 -
混用原始 TreeMap 和包装后的 map:若保留了原始
TreeMap tm引用,并直接调用tm.put(),就绕过了所有同步保护 -
认为 subMap/tailMap 自带同步:子视图(如
map.tailMap(100))返回的仍是非同步视图,遍历时仍需synchronized(map) -
高并发写密集场景下使用:因采用全局独占锁,多个线程同时 put 会排队阻塞,QPS 上升后延迟明显,此时应换用
ConcurrentSkipListMap
何时选它?何时换方案?
它适合快速落地、低维护成本的场景:
- ✅ 推荐用:应用启动时加载静态字典(国家码、状态码)、配置项只读为主、团队已统一使用
synchronizedList/synchronizedSet - ✅ 单元测试中构造共享测试数据,无需引入额外依赖
- ❌ 慎用:高频计数器、实时统计、定时清理过期项(需边遍历边删)、要求弱一致性迭代器
- ➡️ 替代建议:需要真正高并发 + 排序能力 →
ConcurrentSkipListMap;仅需不可变视图 →Collections.unmodifiableSortedMap()
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











