6

仅限 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必须声明以防止优化器重写getInstance volatile() 如下(这在顺序程序中是正确的):

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() 静态。我认为这不会改变答案的有效性;你们都知道我的意思。

4

5 回答 5

15

是的,instance应该声明volatile。即使这样,也建议不要使用双重检查锁定。它(或者准确地说,Java 内存模型)曾经有一个严重的缺陷,允许发布部分实现的对象。这已在 Java5 中得到修复,但 DCL 仍然是一个过时的习语,不再需要使用它 - 改用惰性初始化持有者习语

来自Java Concurrency in Practice,第 16.2 节:

DCL 的真正问题是假设在没有同步的情况下读取共享对象引用时可能发生的最糟糕的事情是错误地看到一个陈旧的值(在这种情况下,null);在这种情况下,DCL 习惯用法通过在持有锁的情况下再次尝试来补偿这种风险。但最坏的情况实际上要糟糕得多 - 可以看到引用的当前值但对象状态的陈旧值,这意味着可以看到对象处于无效或不正确的状态。

JMM(Java 5.0 及更高版本)中的后续更改使 DCL 能够工作if resource is made volatile,并且这对性能的影响很小,因为volatile读取通常只比非易失性读取稍微贵一点。然而,这是一个实用性已基本消失的习语 - 推动它的力量(缓慢的非竞争同步,缓慢的 JVM 启动)不再起作用,使其作为优化的效果降低。惰性初始化持有者习语提供相同的好处并且更容易理解。

于 2010-08-30T15:07:08.583 回答
1

是的,实例需要在 Java 中使用双重检查锁定是易失的,因为否则 MySingleton 的初始化可能会将部分构造的对象暴露给系统的其余部分。线程在到达“同步”语句时也会同步,这也是事实,但在这种情况下为时已晚。

Wikipedia和其他几个 Stack Overflow 问题对“双重检查锁定”进行了很好的讨论,所以我建议阅读它。我还建议不要使用它,除非分析显示在这个特定代码中确实需要性能。

于 2010-08-30T15:04:07.807 回答
1

对于 Java 单例的延迟初始化,有一个更安全、更易读的习惯用法:

class Singleton {

  private static class Holder {
    static final Singleton instance = create();
  }

  static Singleton getInstance() { 
    return Holder.instance; 
  }

  private Singleton create() {
    ⋮
  }

  private Singleton() { }

}

如果您使用更详细的双重检查锁定模式,则必须声明 field volatile,正如其他人已经指出的那样。

于 2010-08-30T15:40:04.780 回答
0

http://www.javaworld.com/jw-02-2001/jw-0209-double.html

于 2010-08-30T15:01:48.273 回答
0

不,它不一定是易变的。请参阅如何解决 Java 中的“双重检查锁定已损坏”声明?

以前的尝试都失败了,因为如果你可以欺骗 java 并避免 volatile/sync,java 可以欺骗你并给你一个不正确的对象视图。然而新的final语义解决了这个问题。如果你得到一个对象引用(通过普通读取),你可以安全地读取它的final字段。

于 2010-08-30T20:29:56.943 回答