-1

我在这里找到了以下代码:http ://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

我试图理解为什么在某些情况下这不起作用。我阅读了“微妙”问题的解释,使用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...
}

基本上,我是否正确地假设这会失败,因为块中的helper == null检查synchronized有可能失败,因为它可能是“部分”构建的?如果对象是部分构造的,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 之后内存模型的变化,我也不太明白这与它有什么关系:-(。

先谢谢了!

4

2 回答 2

4

我是否正确地假设这会失败,因为同步块中的 helper == null 检查有可能失败,因为它可能在那时“部分”构建?

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

于 2012-09-16T17:34:57.993 回答
1

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

希望有帮助!否则,我可以推荐一杯非常浓的咖啡,一个非常安静的房间,然后阅读Java 内存模型,它解释了它是如何工作的以及线程之间的交互。我想你会惊讶于一个线程需要将它的写入(共享)内存传递给其他线程以及 JVM 可以执行的读取和写入重新排序的情况如此之少!

令人兴奋的阅读!

于 2012-09-16T17:27:28.950 回答