仅限Java 5及更高版本。假设一个多处理器共享内存计算机(你现在可能正在使用它)。
这是一个懒惰初始化单例的代码:
public final class MySingleton {
private static MySingleton instance = null;
private MySingleton() { }
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
if (instance == null) {
instance = new MySingleton();
}
}
}
return instance;
}
}
instance
是否必须声明volatile
以防止优化器重写getInstance(),如下所示(在顺序程序中这是正确的):
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
// instance must be null or we wouldn't be here (WRONG!)
instance = new MySingleton();
}
}
}
假设优化器没有重写代码,如果instance
未声明volatile
,则在退出synchronized
块时仍可保证刷新到内存,并从内存中读取何时输入synchronized
块?
编辑:我忘了让getInstance()静态。我不认为这会改变答案的有效性;你们都知道我的意思。
答案 0 :(得分:15)
是的,instance
应声明为volatile
。即便如此,建议不要使用双重检查锁定。它(或者确切地说,Java内存模型)曾经有一个严重的缺陷,允许发布部分实现的对象。这已经在Java5中得到修复,但DCL仍然是一个过时的习惯用语,不再需要使用它了 - 请改用lazy initialization holder idiom。
来自Java Concurrency in Practice,第16.2节:
DCL的真正问题是假设在没有同步的情况下读取共享对象引用时可能发生的最糟糕的事情是错误地看到陈旧值(在这种情况下,
null
);在这种情况下,DCL惯用法通过再次尝试锁定来补偿这种风险。但最糟糕的情况实际上更糟糕 - 可以看到参考的当前值,但是对象状态的陈旧值,这意味着可以看到对象处于无效或不正确的状态。JMM(Java 5.0及更高版本)中的后续更改已使DCL在
resource
成为volatile
的情况下工作,并且此后的性能影响很小{ {1}}读取通常仅比非易失性读取略贵。然而,这是一个习惯已经基本通过的习惯用语 - 激发它的力量(缓慢的无竞争同步,慢速JVM启动)不再起作用,使其作为优化效率降低。懒惰的初始化持有者习语提供了相同的好处,并且更容易理解。
答案 1 :(得分:1)
是的,在Java中使用双重检查锁定实例需要是易失性的,因为否则MySingleton的初始化可能会将部分构造的对象暴露给系统的其余部分。当线程达到“同步”语句时,它们也会同步,但在这种情况下为时已晚。
Wikipedia以及其他一些Stack Overflow问题对“双重检查锁定”进行了很好的讨论,因此我建议您阅读它。我还建议不要使用它,除非分析显示在这个特定代码中真正需要性能。
答案 2 :(得分:1)
对于Java单例的延迟初始化,有一种更安全,更易读的习惯用语:
class Singleton {
private static class Holder {
static final Singleton instance = create();
}
static Singleton getInstance() {
return Holder.instance;
}
private Singleton create() {
⋮
}
private Singleton() { }
}
如果您使用更详细的Double-Checked Locking模式,则必须声明字段volatile
,正如其他人已经注意到的那样。
答案 3 :(得分:0)
答案 4 :(得分:0)
没有。它不一定是不稳定的。 见How to solve the "Double-Checked Locking is Broken" Declaration in Java?
以前的尝试都失败了,因为如果你可以欺骗java并避免使用volatile / sync,那么java会欺骗你并给你一个不正确的对象视图。但是新的final
语义解决了这个问题。如果你得到一个对象引用(通过普通读取),你可以安全地读取它的final
字段。