非空 class 成员字段的双重检查锁定

Double-checked locking of non-null class member field

我知道 classic double-checked locking idiom for Java,它首先检查给定字段是否为 null,如果是,则获取 [=40= 上的锁] 具有以下字段:

// Double-check idiom for lazy initialization of instance fields
// See Pascal Thivent's original answer <
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;
}

但是,在 field 永远不会为 null 的情况下(而是引用“null object”值,它被懒惰地替换为 "non-null object"),[= 上的同步可以吗? 15=] 细化为 synchronized(field)?

例子

我有一个非常大的 HugeObject,但可以很容易地重新创建。我希望垃圾收集器能够在内存开始 运行 不足的情况下丢弃此实例,因此我用 Reference<HugeObject> 保存它,并将其初始化为 Reference<HugeObject> field = new SoftReference<>(null)。我宁愿避免锁定整个对象 this,这样即使在 field 初始化期间也可以调用不影响 field 状态的方法。那么是否有可能简单地在 这个初始 "null object" 已经实例化的 "non-null object" 上获得锁定,或者这会导致微妙的、不需要的并发影响吗?请参阅下面的代码:

private volatile Reference<HugeObject> field = new SoftReference<>(null);

HugeObject getField() {
    HugeObject result = field.get();
    if (result == null) {
        synchronized(field) {
            result = field.get();
            if (result == null) {
                result = computeFieldValue();
                field = new SoftReference<>(result);
            }
        }
    }
    return result;
}

如果不想在this同步,可以使用其他参考。但该参考需要保持不变。在您的示例中,您正在锁定 field 并重新分配 - 因此如果两个线程对 field.

具有不同的值,则它们可以同时执行您的方法

一个标准的解决方案是使用专用锁:

private final Object lock = new Object();

//...

synchronized(lock) { ... }

您绝对不想锁定空可变成员。大多数人锁定 this 或 class。但是,如果您不想这样做,assylias 的解决方案非常适合您的用例。您还可以查看 java.util.concurrent.locks 包。它提供了在不使用 synchronized 块的情况下手动锁定的方法。也许 ReentrantLock 适合你?该包还有一些很酷的东西,比如条件变量和自旋锁(虽然可能不是你在这里使用的)。另外,我写了一篇关于单例模式和双重检查锁定的使用以及您可能感兴趣的 "holder" 模式的文章。 https://medium.com/@michael.andrews/deconstructing-the-singleton-b5f881f85f5