2026年物业门控五金耗材推荐榜:中企创联工业品,小区/写字楼/物业多场景门控配件全覆盖
2026/3/2 14:07:46
为什么要这么做?因为在并发场景下,双重检查锁(DCL)确实存在严重问题——
helper=newHelper();// 这不是原子操作实际上包含三个步骤:
Helper对象分配内存空间helper变量问题在于:步骤2和步骤3可能被JVM重排序,导致另一个线程看到一个未完全初始化的对象。
// 线程1执行helper=newHelper();// 重排序后:分配内存 → 赋值引用 → 初始化// 在线程1赋值引用后、初始化前,线程2进入if(helper==null){// helper不为null,但对象未初始化!// 跳过同步块returnhelper;// 返回一个半成品对象!}volatile关键字classSingleton{privatevolatileHelperhelper=null;// 关键:添加 volatilepublicHelpergetHelper(){if(helper==null){// 第一次检查(无锁)synchronized(this){// 加锁if(helper==null){// 第二次检查(有锁)helper=newHelper();// 安全初始化}}}returnhelper;}}volatile如何解决问题volatile写之前的所有操作,都不会被重排序到写之后volatile读之后的所有操作,都不会被重排序到读之前helper = new Helper()的初始化操作(构造函数调用)会在赋值之前完成volatile变量时,新值会立即被刷新到主内存volatile变量时,会从主内存重新加载最新值根据JSR-133(Java内存模型增强):
volatile变量的写操作 happens-before 于后续对该变量的读操作publicclassSafeDoubleCheckedLocking{// 必须使用 volatile 修饰privatevolatileResourceresource;publicResourcegetResource(){// 第一次检查:大多数情况不需要同步,提升性能Resourceresult=resource;if(result==null){// 同步块:确保只有一个线程初始化synchronized(this){result=resource;if(result==null){// volatile 保证初始化完成前不会发布引用resource=result=newResource();}}}returnresult;}staticclassResource{// 资源类的定义publicResource(){// 复杂的初始化逻辑}}}volatile语义不完整volatile的语义| 方案 | 优点 | 缺点 |
|---|---|---|
| volatile + DCL | 性能好(大多数情况无锁) | 代码稍复杂,需要JDK5+ |
| 静态内部类 | 简洁安全,延迟加载 | 无法传参初始化 |
| 枚举单例 | 绝对安全,防止反射攻击 | 不够灵活 |
| synchronized方法 | 简单安全 | 每次访问都同步,性能差 |
优先考虑其他单例模式
必须使用DCL时
// 确保:// 1. 使用 volatile// 2. 使用JDK5+// 3. 两次检查都不省略性能考量