Collections.synchronizedMap 本身不会直接导致死锁,但其底层 Map 若配合手动加锁或嵌套同步块且锁顺序不一致,易引发死锁;它用 map 自身作锁,仅保证单方法原子性,不防复合操作问题,且锁对象暴露易被外部误用。

Collections.synchronizedMap 本身不会直接导致死锁,但它包裹的底层 Map(比如 HashMap)若在多线程中被不当使用,尤其配合手动加锁或嵌套同步块时,容易因锁顺序不一致而引发死锁。关键不在 synchronizedMap 的封装逻辑,而在开发者对锁边界的误判和跨临界区操作。
理解 synchronizedMap 的锁机制
synchronizedMap 返回的是一个包装器(Wrapper),所有 public 方法(如 get、put、remove)都用同一个对象锁(即 map 对象自身)同步。这意味着:
- 每次调用 put() 或 get() 都会独占这个锁,串行执行;
- 它只保护单个方法调用,不保证复合操作(如“检查后执行”)的原子性;
- 锁对象是 map 实例本身(this),不是内部私有对象,因此可能被外部代码意外用于其他同步场景。
典型双锁死锁场景还原
死锁常发生在两个线程分别按不同顺序获取两把锁。synchronizedMap 的锁(mapA)一旦与另一把锁(比如 lockB)交叉使用,就埋下隐患:
- 线程1:先 synchronized(mapA) { ... },再 synchronized(lockB) { ... }
- 线程2:先 synchronized(lockB) { ... },再 synchronized(mapA) { ... }
此时若线程1持 mapA 等 lockB,线程2持 lockB 等 mapA,即构成经典环路等待——死锁成立。注意:synchronizedMap 的锁参与了这个环,但根源是锁顺序未统一。
立即学习“Java免费学习笔记(深入)”;
排查双锁顺序的关键点
定位问题需聚焦锁的获取路径,而非仅看 Collections 工具类:
- 用 jstack 抓取线程堆栈,查找 BLOCKED 状态线程,确认它们各自等待哪把锁(例如 waiting to lock );
- 反查锁对象的 identity hashcode,比对哪些代码路径持有该锁(可通过 IDE 调试或添加日志输出 System.identityHashCode(lockObj));
- 重点审查:是否在 synchronized(map) 块内调用了外部可重入或阻塞的方法(如远程调用、IO、另一个 synchronized 集合操作);
- 检查是否将 synchronizedMap 实例作为锁对象暴露给其他模块(例如 public final Map m = Collections.synchronizedMap(new HashMap()); —— 外部可能直接 synchronized(m))。
避免死锁的实用建议
根本原则是减少锁粒度、统一锁序、避免嵌套:
- 优先用 ConcurrentHashMap 替代 synchronizedMap,它无全局锁,天然规避此类死锁;
- 若必须用 synchronizedMap,确保它的使用严格限于单个方法调用,绝不把它当通用锁对象;
- 涉及多资源时,约定全局锁顺序(例如始终先 lockB 后 mapA),并在文档中明确;
- 对复合操作,用显式 ReentrantLock + tryLock(timeout) 主动规避无限等待,而不是依赖 synchronized。


















