以下是我的单例类,我使用双重检查锁定而不使用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;
}
}
据我所知,这是线程安全的。如果有人认为,这不是线程安全的,有人可以详细说明为什么他/她认为这不是线程安全的吗? 感谢。
答案 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编译器或热点编译器可以完全优化它。