Java 双重检查锁定

我最近偶然发现了一篇文章,讨论了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;
    }
}

答案 1

双重检查锁定已损坏。由于 initialized 是一个基元,它可能不需要它是易失性的才能工作,但是没有什么可以阻止初始化在初始化实例之前被视为对非同步化代码的 true。

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

双重检查锁定的正确解决方案是使用易失性(在实例字段上)并忘记初始化的布尔值,并确保使用JDK 1.5或更高版本,或者在最终字段中初始化它,如链接文章和Tom的答案中所述,或者只是不使用它。

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


答案 2

如果 是 ,则有效。就像有趣的效果一样,与参考关系不大,而是我们可以对其他数据说什么。字段和对象的设置被强制进行 - 在写入 之前。当通过短路使用缓存值时,读取发生在读取和通过引用到达的对象之前。拥有单独的标志没有显着差异(除了它会导致代码更加复杂)。initializedvolatilesynchronizedvolatileinstanceTestinitializedinitializeinstanceinitialized

(对于不安全的发布,构造函数中的字段规则略有不同。final

但是,在这种情况下,您应该很少看到该错误。第一次使用时遇到麻烦的机会很小,这是一场不重复的比赛。

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

private static final Test instance = new Test();

public static Test getInstance() {
    return instance;
}

推荐