为什么不推荐使用 DCL(双重检查加锁)

双重检查加锁 被熟知为“懒汉式”单例模式的实现,下文将统一称之为 DCL。

早期 JVM 中因为同步的开销巨大,为了降低实现单例模式中同步带来的开销,人们想出了很多技巧,DCL 便是其中一种。一段常规的 DCL 实现的单例模式如下:

public class DoubleCheckedLocking {
    
    private static SingleInstance singleInstance;

    public static SingleInstance getInstance() {
        if (singleInstance == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (singleInstance == null) {
                    singleInstance == new SingleInstance();
                }
            }
        }
    }
}
1. 描述

DCL 主要是为了使用延迟初始化来降低“饿汉式”单例模式对 JVM 启动时的性能影响,为了方便解释其中存在的问题,这里将上面代码块中的两个 if 语句分别成为 if.1if.2

2. 情景

假设此时有两个线程 A 和 B 都需要获取 SingleInstance,并且 A 线程进入到 if.2 内,B 线程刚开始 if.1 的判断。也就是说,在 A 线程在执行初始化的过程中,B 线程读取了线程间共享变量(singleInstance)。这种情况下,B 线程很有可能读取到一个初始化并未完成的非空的引用(singleInstance 被部分构造,产生原因后面会有讨论)。

比如 SingleInstance 对象中有一个字段 id:String,并且在构造函数中为该字段进行赋值。在上文描述的情况下就是:singleInstance 实例已经是一个非空的引用,new SingleInstance("110") 操作已经生效,但是并没有全部完成。singleInstance.getId() 返回的仍然是 null,而不是构造函数中传入的 ”110“。
这种情况下,B 线程不会再创建重复的实例,但是会拿着一个无效的对象进行后续操作,可能会在程序中造成更严重的错误。

3. 为什么会出现部分构造的对象

简单来说是因为无序写入(out-of-order writes)。
如果构造函数写入非 final 字段,则不必立即将它们提交到内存,甚至可以在单例变量之后提交。构造函数其实已经完成,但这并不意味着所有写入对其它线程可见。
部分构造就是这种情况的一个糟糕体现,singleInstance 引用已对其它线程可见,但对象的内容singleInstance.getId() 对其它线程并不可见。就是因为对象构造过程中一系列指令写入内存的乱序,导致了失效对象的产生。

4. 解决方法

在 Java 5.0 之后,使用 volatile 来修饰 singleInstance 实例,就不会产生指令重排序的情况,这样 DCL 也就可以正常工作了。
但因为有了更加方便与安全的替代方式,DCL 也没有什么特别的优势,便被废弃了。

5. 延迟初始化占位类模式

使用延迟初始化占位类模式,可以在保证延迟加载优点的同时,得到 Java 语言层面提供的安全保障。有兴趣的同学可以搜一下,资料很多。当然也包括 Java 内存模型相关,可以了解到更多 out-of-order writes 相关的原理。

参考
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 面试必背 会舍弃、总结概括——根据我这些年面试和看面试题搜集过来的知识点汇总而来 建议根据我的写的面试应对思路中的...
    luoyangzk阅读 6,838评论 6 173
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,304评论 4 34
  • 时间:2018-04-08 作者:梦妮萱 白色鬼针草,又名粘人草。为什么要提起它?那是因为这个清明假期,我去了一趟...
    止懐阅读 2,166评论 0 0
  • 是夜,梦中:男子倾城一笑,女子仿若醉了一般,摇摇晃晃,待男子走进,女子抱住男子 “帝君,说实话,凤九喜欢你,喜欢你...
    凤华正茂阅读 6,517评论 7 55
  • 亲爱的宝贝: 今天带你去理发,从理发店出来出来你生气地问我:我不是说要剪的短短的嘛?怎么只剪了这么少呢?咳...
    晓寒iyoyo阅读 178评论 0 0