双重检查锁定而不使用volatile-keyword并且不同步整个getInstance()方法

时间:2018-05-24 13:46:32

标签: java multithreading singleton double-checked-locking safe-publication

以下是我的单例类,我使用双重检查锁定而不使用volatile关键字而不同步整个getInstance()方法:

public class MySingleton {

    private static MySingleton mySingleton;

    public static MySingleton getInstance() {
        if(mySingleton == null) {
            synchronized(MySingleton.class) {
                if(mySingleton == null) {
                    MySingleton temp = new MySingleton();
                    mySingleton = temp;
                }
            }
        }

        return mySingleton;
    }
}

据我所知,这是线程安全的。如果有人认为,这不是线程安全的,有人可以详细说明为什么他/她认为这不是线程安全的吗? 感谢。

3 个答案:

答案 0 :(得分:0)

此处我们有synchronized次写入和non-synchronized次阅读。我们不保证读取将获得写入设置的值。所以这段代码很麻烦。但它是一个非常微妙的错误,特别是在多核CPU中。

如果由于某种原因需要继续使用上述代码,一种解决方案是将变量声明为volatile,如下所示:

private static volatile MySingleton mySingleton;

或者读取变量synchronized。他们中的任何一个都会以牺牲性能为代价来解决您的问题。

另一个重要的一点是,您的temp变量在此处没有用处。它只是一个冗余的代码,什么都不做。而是直接将其分配给mySingleton变量,如此,

mySingleton = new MySingleton();

使用enum类型制作单例,可以很容易地解决上述所有问题。由于枚举本质上是Serializable,因此我们不需要使用Serializable接口来实现它。反思问题也不存在。因此,100%保证JVM中只存在Singleton的一个实例。因此,建议使用此方法作为在Java中制作单例的最佳方法。

答案 1 :(得分:0)

在我阅读所有评论之前,我还没有意识到这个问题。问题是各种优化过程(编译器,热点,无论如何)都会重写代码。你的" temp"解决方案很容易被删除。我发现很难相信构造函数可以返回一个部分对象,但如果知识渊博的贡献者这么说,我就会相信他们的意见。

答案 2 :(得分:0)

  

是的,但我正在使用" temp"变量。它是否解决了部分创建的对象"问题

没有。它没有。

假设某个线程A调用getInstance(),并最终创建一个新实例并分配mySingleton变量。然后线程T出现,调用getInstance()并看到mySingleton不是null

此时,线程T未使用任何同步。如果没有同步,Java语言规范(JLS)不要求线程T以与线程A相同的顺序查看线程A所做的分配。

假设单例对象有一些成员变量。线程A显然必须在将引用存储到mySingleton之前已初始化这些变量。但是JLS允许线程T看到mySingleton != null,但仍然看到成员变量处于未初始化状态。在一些多核平台上,它实际上可以这样发生。

首先将对象引用分配给本地temp变量并不会改变任何内容。事实上,正如Steve11235指出的那样,temp变量甚至可能实际上不存在于字节代码或本机指令中,因为Java编译器或热点编译器可以完全优化它。