Java中此段代码实现的double checked locking是否存在线程安全缺陷?
结论
你的判断完全正确,该双重检查锁定实现确实存在你描述的半初始化对象逃逸问题,同时还有额外的线程安全风险。
问题根因
- 指令重排导致半初始化对象发布
在Java内存模型中,new DoorControlManager(door)操作会被拆分为三个独立步骤:分配对象内存空间、将内存地址赋值给对象引用、执行构造函数初始化字段。在没有正确同步的场景下,编译器和处理器允许对这三个步骤做重排序,可能出现「将地址赋值给引用」的操作跑到「构造函数执行」前面的情况。此时未完成初始化的对象会被提前放入HashMap,其他线程在同步块外执行containsKey判断时会认为对应实例已经存在,直接返回未初始化完成的对象,所有字段都为默认零值。 - HashMap非线程安全带来额外风险
代码中用到的HashMap本身不是线程安全实现,同步块外的containsKey操作执行时,如果刚好有其他线程在同步块内执行put操作触发HashMap扩容、结构调整,外部的读操作可能出现死循环、抛出异常、返回错误结果的问题。 - 代码笔误
方法入参名为door,但实际put、get时使用的键是slotId,代码无法直接编译通过,实际使用时需要先修正变量名一致性问题。
修复方案
如果是JDK8及以上版本,推荐直接用ConcurrentHashMap替代手动实现的DCL,简化实现且没有线程安全问题:
public class DoorControlManager { private static ConcurrentHashMap<Integer, DoorControlManager> mInstances = new ConcurrentHashMap<>(); public static DoorControlManager getInstance(int door) { return mInstances.computeIfAbsent(door, DoorControlManager::new); } // 其余业务代码 }
如果必须保留原有DCL写法,可以将实例包装为带volatile修饰的对象避免重排序问题,同时把HashMap的所有读写操作都放在同步块内,或者直接替换为ConcurrentHashMap。
内容的提问来源于stack exchange,提问作者Willy
相关产品推荐
相关产品推荐

