Java 双重检查锁定中的 volatile

volatile in double-checked locking in Java

据我所知,这是 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 访问。

我的想法如下:synchronized 引入了 happens-before 关系。初始化 _barInstance 的线程将其值写入主内存,离开同步块。所以不会有 _barInstance 的双重初始化,即使它不是 volatile:其他线程在 _barInstance 的本地副本中有 null(得到 true在第一次检查中),但在进入同步块后的第二次检查中必须从主内存中读取新值(获取 false 并且不进行重新初始化)。所以唯一的问题是过多的每线程锁获取。

据我了解,它在 CLR 中是正确的,我相信它在 JVM 中也是正确的。我说得对吗?

谢谢。

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

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

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

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