Java双重检查锁定

39 java singleton multithreading synchronization double-checked-locking

我碰巧发表了一篇文章,最近讨论了Java中的双重检查锁定模式及其陷阱,现在我想知道我多年来一直使用的那种模式的变体是否会受到任何问题的影响.

我已经查看了很多关于这个主题的帖子和文章,并了解了获取对部分构造对象的引用的潜在问题,据我所知,我认为我的实现不受这些问题的影响.以下模式是否有任何问题?

而且,如果没有,为什么人们不使用它?我在这个问题的任何讨论中都没有看过它.

public class Test {
    private static Test instance;
    private static boolean initialized = false;

    public static Test getInstance() {
        if (!initialized) {
            synchronized (Test.class) {
                if (!initialized) {
                    instance = new Test();
                    initialized = true;
                }
            }
        }
        return instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

Yis*_*hai 25

双重检查锁定被打破.由于initialized是一个原语,因此它可能不需要它是易失性的,但是在初始化实例之前,没有什么能阻止初始化被视为对非同步代码的真实性.

编辑:为了澄清上面的答案,原始问题询问使用布尔值来控制双重检查锁定.如果没有上面链接中的解决方案,它将无法正常工作.您可以仔细检查实际设置布尔值的锁定,但在创建类实例时仍然存在有关指令重新排序的问题.建议的解决方案不起作用,因为在非同步块中看到初始化的布尔值为true后,实例可能无法初始化.

双重检查锁定的正确解决方案是使用volatile(在实例字段上)并忘记初始化的布尔值,并确保使用JDK 1.5或更高版本,或者在最终字段中初始化它,如链接中详细说明的那样文章和汤姆的回答,或者只是不使用它.

当然,整个概念似乎是一个巨大的过早优化,除非你知道你将获得大量的线程争用获得这个Singleton,或者你已经分析了应用程序,并已经看到这是一个热点.

  • 双重检查锁定*被打破了.不要忘记阅读链接文档末尾标题为"在新的Java内存模型下"的部分,其中显示了它在JDK 1.5及更高版本中的工作方式和原因(即现在已超过五年). (37认同)

Tom*_*ine 16

如果initialized是的话,这将有用volatile.就像synchronized有趣的效果volatile并没有真正与参考一样,我们可以说其他数据.在写入之前强制设置instance字段和Test对象.当通过短路使用缓存值时,读取发生在读取和通过引用到达的对象之前.具有单独的标志没有显着差异(除了它导致代码中更复杂).initializedinitializeinstanceinitialized

(final不安全发布的构造函数中的字段规则略有不同.)

但是,在这种情况下,您应该很少看到错误.第一次使用时遇到麻烦的可能性很小,而且是一次非重复比赛.

代码过于复杂.你可以把它写成:

private static final Test instance = new Test();

public static Test getInstance() {
    return instance;
}
Run Code Online (Sandbox Code Playgroud)

  • 考虑到实际上很少需要懒惰的初始化,以及更少的类级懒惰还不够,我只是无法理解任何人如何能够证明所有关于双重检查锁定等等的讨论中的脑力劳动. (7认同)
  • @rsp这仍然是懒惰的:虽然你不访问该类,但它不会初始化.但确实如此,在某些情况下,我们希望懒惰真的在最后一刻...... (6认同)
  • 但要注意在应用程序启动时创建世界.懒惰初始化有其用途. (3认同)
  • 迈克尔:人们喜欢疯狂的优化!你考虑一下好.出于某些目的(比如实现`java.util.concurrent`),它非常有用.几乎总是,简单的代码最适合饲养员. (3认同)

mat*_*t b 12

双重检查锁定确实被破坏了,问题的解决方案实际上比这个习惯用法更简单 - 只需使用静态初始化程序.

public class Test {
    private static final Test instance = createInstance();

    private static Test createInstance() {
        // construction logic goes here...
        return new Test();
    }

    public static Test getInstance() {
        return instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

保证在JVM第一次加载类时,以及在类引用可以返回到任何线程之前执行静态初始化程序 - 使其本身具有线程安全性.

  • 基于http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html#44557和http://java.sun.com/docs/,我认为这不是真的. books/jls/second_edition/html/execution.doc.html#44630 - 最终修饰符似乎只影响初始化字段的顺序.但是,无论如何,这个字段应该是最终的,所以无论如何我都更新了代码. (2认同)

Sud*_*ari 5

这就是为什么双重检查锁定被打破的原因.

同步保证,只有一个线程可以输入代码块.但它并不能保证在同步部分内完成的变量修改对其他线程是可见的.只有进入同步块的线程才能保证看到更改.这就是为什么双重检查锁定被打破的原因 - 它在读者方面没有同步.读取线程可以看到单例不为空,但单例数据可能未完全初始化(可见).

订购由提供volatile.volatile保证排序,例如写入volatile单例静态字段,保证在写入易失性静态字段之前完成对单例对象的写入.它不会阻止创建两个对象的单例,这是通过同步提供的.

类最终静态字段不需要是易失性的.在Java中,JVM负责这个问题.

请参阅我的帖子,回答一个真实世界的Java应用程序中的Singleton模式和双重检查锁定,说明了一个关于双重检查锁定的单例的例子,它看起来很聪明但是被破坏了.