Java双重锁定——有人能更简单地解释一下直觉为什么不会';不行?

Java双重锁定——有人能更简单地解释一下直觉为什么不会';不行?,java,multithreading,concurrency,volatile,Java,Multithreading,Concurrency,Volatile,我在这里找到了以下代码: 我试图理解为什么在某些情况下,这是行不通的。我阅读了关于“微妙”问题的解释,使用volatile可以解决问题,但我有点困惑 // Broken multithreaded version // "Double-Checked Locking" idiom class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) {

我在这里找到了以下代码:

我试图理解为什么在某些情况下,这是行不通的。我阅读了关于“微妙”问题的解释,使用
volatile
可以解决问题,但我有点困惑

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

    // other functions and members...
}
基本上,由于
synchronized
块中的
helper==null
检查有可能失败,因为它可能在该点“部分”构造,因此我假设这将失败,对吗?如果对象是部分构造的,java不返回null吗?这就是问题所在吗

无论如何,我知道执行双重检查锁定不是一个很好的实践,但我只是在理论上好奇为什么上面的代码会失败,为什么volatile(加上分配一个局部变量)会修复这个问题?这是我从某处得到的一些代码

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null) // Second check (with locking)
                field = result = computeFieldValue();
        }
    }
    return result;
}
我知道已经有上千篇关于这一点的帖子了,但解释似乎提到了1.5版之后内存模型的变化,我也不太明白这与它有什么关系:-(


感谢高级!

您需要声明字段
volatile
,因为这将强制“刷新”对字段的写入到主内存。否则JVM规范允许每个线程保留其本地版本的字段,从不将其写操作与其他线程通信。这通常很好,因为它允许在JVM中进行积极的优化

希望这会有帮助!否则我可以建议你喝一杯浓咖啡,在一个安静的房间里,然后读一读说明它是如何工作的以及线程之间的交互。我想你会惊讶于一个线程需要与之交流写操作的情况是如此之少(共享)内存分配给其他线程,以及JVM可以执行的读写重新排序

令人兴奋的阅读

由于同步块中的helper==null检查有可能失败,因为它可能在该点被“部分”构造,因此我是否正确地假设这将失败


是的,您是对的。这在中进行了解释。
helper=new helper()
包括3个步骤:内存分配、调用构造函数和赋值。JIT编译器可以在内存分配(返回对新对象的引用)后对指令重新排序和赋值但是在构造函数调用之前。使用volatile可以防止重新排序。

关于volatile不太正确:“synchronized”也会刷新到主内存。您永远不能使用volatile,只能使用“synchronized”并拥有正确的程序。此外,可以看到部分构造的对象,请参阅我的答案中的参考。问题,这可能是一个愚蠢的问题,b但是为什么java不要求在将==null计算为false之前完成所有三个步骤?因为比较null在不同的线程中。如果它在同一个线程中,那么您将永远看不到部分构造的对象。@K2xL java的内存模型说,将赋值重新排序到内存位置和初始化是有效的能够作为热点的性能优化,除非您另有说明。您使用安全发布(volatile、final字段等)换句话说。这种优化的能力很重要,如果它在默认情况下是线程安全的,那么它也会慢得多。@JedWesley Smith有趣的是,我想知道在什么情况下,提前分配内存位置会更快……除了指令重新排序,DCL的另一个经典问题(在Java和其他语言中都会发生这种情况)在多处理器系统中,缓存会导致一个处理器所做的更改在另一个处理器上被认为是无序的。有关Java中DCL问题的完整描述,请参阅cs.umd.edu/~pugh/Java/memoryModel/DoubleCheckedLocking.html。