Java中的双重检查锁定中的volatile

4

据我了解,这是Java中双重检查锁定模式的正确实现(自Java 5以来):

class Foo {
    private volatile Bar _barInstance;
    public Bar getBar() {
        if (_barInstance == null) {
            synchronized(this) { // or synchronized(someLock)
                if (_barInstance == null) {
                    Bar newInstance = new Bar();
                    // possible additional initialization
                    _barInstance = newInstance;
                }
            }
        }
        return _barInstance;
    }
}

我想知道没有volatile是否会是一个严重的错误,还是只是一种轻微的缺陷,假设_barInstance只通过getBar访问。
我的想法是:同步引入了“发生在-之前”的关系。初始化_barInstance的线程将其值写入主内存并离开同步块。因此,即使_barInstance不是volatile,也不会出现双重初始化:其他线程在其本地副本中具有null(在第一次检查时得到true),但必须在进入同步块后从主内存中读取新值(在第二次检查时得到false并且不进行重新初始化)。因此,唯一的问题就是每个线程都会获得多余的一次锁定。
据我所知,这在CLR中是正确的,而且我相信在JVM中也是正确的。我对吗?
谢谢。

@Todd 可能是这样,但是回答中只有“volatile很重要”和“不是线程安全”的说法,没有解释。我真的想详细了解这个情况 :) - Ivan Yurchenko
3
叹气。阅读Jeremy Manson的文章:《双重检查锁定》、《Java中volatile的含义》和《双重检查锁定及其问题》。 - David Conrad
@DavidConrad 谢谢,我可能明白了。JVM 可以重新排序构造函数指令,使其在分配 _barInstance 并且后续线程可以看到_barInstance 已初始化之前执行。当它们看到它不是 null 时,它们不会获取锁并且 happens-before…嗯,就是没有发生 :) 然后它们使用未初始化的对象。另一方面,当 _barInstancevolatile 的时候,写和读始终保持 happens-before。正确吗? - Ivan Yurchenko
1个回答

7

如果不使用volatile,可能会导致以下情况的错误:

  • 线程1进入getBar()并发现_barInstancenull
  • 线程1尝试创建一个Bar对象并更新对_barInstance的引用。由于某些编译器优化,这些操作可能会被乱序执行。
  • 同时,线程2进入getBar(),看到非空的_barInstance,但可能会在_barInstance对象的成员字段中看到默认值。它实际上看到了一个部分构建的对象,但引用不是null。

volatile修饰符将禁止相对于任何先前读取或写入变量_barInstance进行写入或读取。因此,它将确保线程2不会看到部分构造的对象。

更多详情请见:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html


我现在明白了,谢谢。 - Ivan Yurchenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接