
treemap在多线程未同步访问时可能引发内部红黑树结构损坏,进而触发无限循环,造成线程持续占用cpu而不返回,而非简单的数据不一致。
treemap在多线程未同步访问时可能引发内部红黑树结构损坏,进而触发无限循环,造成线程持续占用cpu而不返回,而非简单的数据不一致。
TreeMap 是 Java 中基于红黑树实现的有序映射(SortedMap),具备 O(log n) 的插入、查找和删除时间复杂度。其内部通过复杂的节点着色、旋转与重平衡逻辑维护树结构的稳定性。然而,TreeMap 并非线程安全类——它既未对临界操作加锁,也未采用无锁原子结构设计。
当多个线程同时调用 put()(或 get()、size() 等方法)且缺乏外部同步时,极有可能因竞态条件破坏红黑树的结构不变量(如父子指针错乱、颜色标记冲突、自环引用等)。更严重的是:某些损坏状态会使 get() 或迭代器在遍历时陷入死循环——例如节点 next 指针意外指向自身,或形成无法终止的环状链表。此时线程将持续执行空转,100% 占用一个 CPU 核心,而不会抛出异常或阻塞,这正是生产环境中“CPU 飙升但无明显报错”的典型诱因。
以下是一个简化复现场景(仅作原理示意,实际触发需高并发压力):
// ⚠️ 危险示例:多线程并发写入非同步 TreeMap
Map<String, Integer> unsafeMap = new TreeMap<>();
ExecutorService exec = Executors.newFixedThreadPool(8);
for (int i = 0; i < 1000; i++) {
final int idx = i;
exec.submit(() -> unsafeMap.put("key-" + idx, idx));
}
exec.shutdown();
exec.awaitTermination(10, TimeUnit.SECONDS);
// 此后任意线程调用 unsafeMap.get("any") 可能卡死✅ 正确解决方案有且仅有两类:
立即学习“Java免费学习笔记(深入)”;
- 首选:使用线程安全的替代实现 —— ConcurrentSkipListMap。它基于跳表(Skip List)实现,天然支持高并发读写,保持排序性且无锁(lock-free),性能与扩展性远优于同步包装方案;
- 次选:显式同步 —— 用 Collections.synchronizedSortedMap(new TreeMap()) 包装,或手动 synchronized(map) 控制所有访问。但该方式会将并发操作序列化,严重损害吞吐量,仅适用于低频写+简单场景。
⚠️ 特别注意:
- synchronized 仅保护单个方法调用,无法保证复合操作(如 if (!map.containsKey(k)) map.put(k, v))的原子性,仍需额外同步块;
- ConcurrentSkipListMap 不是 TreeMap 的完全语义替代:它不支持 subMap()/headMap() 的强一致性快照视图(其子视图是弱一致性),且比较器需满足 Comparable 合约(不可抛异常);
- 线程转储(thread dump)中若发现大量线程堆栈停留在 TreeMap.getEntry()、TreeMap.get() 或 TreeMap$EntryIterator.next(),基本可锁定为该问题。
总结:TreeMap 的线程不安全性不仅关乎数据可见性,更可能直接引发运行时无限循环——这是 JVM 层面无法自动检测与恢复的底层结构故障。务必在设计阶段明确并发模型,杜绝“先用再同步”的侥幸心理。生产系统中,凡涉及共享可变状态的有序映射,应默认选用 ConcurrentSkipListMap。








