English 中文(简体)
使用双重检查机制重置延迟加载的字段
原标题:
  • 时间:2008-11-20 22:20:17
  •  标签:

考虑“延迟初始化实例字段的双重检查成语”。

// Item 71 in Effective Java copied from this interview with Bloch.
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;
}

我想以安全的方式重置该字段(在我的情况下,强制它再次从数据库加载)。我认为我们可以通过拥有一个重置方法来实现这一点:

void reset() {
   field = null;
}

这是否是重置字段的标准方法?它安全吗?有什么问题吗?我之所以问这个问题,是因为Bloch对于double-checked lazy-loading发出了以下警告:“这个习惯用法非常快速,但也很复杂和微妙,所以不要试图以任何方式修改它。只需复制和粘贴--通常不是一个好主意,但在这里很合适。”

Thanks in advance, Playa from the Himalayas.

最佳回答

是的,这是线程安全的。

同步块的作用是防止多个线程不必要地调用computeFieldValue()。 由于field是易失性的,因此resetgetField中的访问都是良好排序的。

如果第一个检查不为空,将执行getField函数,然后返回result

否则,该锁将被获取,排除任何可能将该字段设置为非null值的其他线程,但允许任何线程将field设置为null。如果有任何线程将field设置为null,那么什么也不应该发生,这就是使线程进入同步块的条件。如果在当前线程检查之后另一个线程已经获取了锁,并将字段设置为非null值,则第二次检查将检测到该情况。

问题回答

我认为这应该是安全的,但仅因为您将字段存储在本地变量中。完成此操作后,即使另一个线程在半途更改字段的值,本地变量引用也不会神奇地变为null。

只要重置方法是上述的reset()方法,似乎这样做会奏效。但是,如果reset()方法实例化一个新的对象(比如下面的示例),你有可能返回与原意不同的结果。

void reset() {
    field = new FieldType();
}

我想这要取决于你对线程安全的确切理解。

你可能最终会遇到情况,第一实例在第二实例后被使用。这可能是可以接受的,也可能不是。

我认为reset()方法不正确。如果你读了第71条,你会发现:

This code may appear a bit convoluted. In particular, the need for the local variable result may be unclear. What this variable does is to ensure that field is read only once in the common case where it’s already initialized.

懒初始化并不意味着字段可能会发生改变。如果在这些运算符之间将字段设置为 null,那么……

FieldType result = field; 如果(result == null){ // 第一个检查(无锁定)

getField() 提供不正确的结果。





相关问题
热门标签