使用双重检查成语重置延迟加载的字段

时间:2008-11-20 22:20:17

标签: java thread-safety locking double-checked-locking

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

// 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;
}

这是重置字段的标准方法吗?安全吗?任何陷阱?我问,因为布洛赫发出了关于双重检查懒惰加载的以下警告:“成语非常快,但也很复杂和细腻,所以不要试图以任何方式修改它。只需复制和粘贴 - 通常不是一个好主意,但在这里是合适的。“

提前致谢, 来自喜马拉雅山脉的Playa。

5 个答案:

答案 0 :(得分:4)

是的,这是线程安全的。

synchronized块用于防止多个线程不必要地调用computeFieldValue()。由于field是易变的,resetgetField中的访问都是有序的。

如果第一次检查非空,则getField完成; <{1}}已退回。

否则,获取锁定,排除可能将该字段设置为非null的任何其他线程,但允许任何线程将result设置为null。如果任何线程将field设置为null,则不应该更改任何内容;这是使线程进入同步块的条件。如果另一个线程在当前线程检查后已经获取了锁,并将该字段设置为非空值,则第二次检查将检测到该值。

答案 1 :(得分:3)

我认为这应该是安全的,但这只是因为你将字段存储在局部变量中。完成此操作后,即使另一个线程正在重置字段的值,也无法将局部变量引用神奇地更改为null。

答案 2 :(得分:0)

只要重置方法是上面列出的reset()方法,这似乎就会起作用。但是,如果reset()方法实例化一个新的Object(如下所示),你最终是否可能返回与你想要的不同的东西?

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

答案 3 :(得分:0)

我想这完全取决于线程安全的含义。

您可能会遇到第二次使用第一个实例的情况。这可能没问题,也可能没有。

答案 4 :(得分:0)

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

此代码可能看起来有点复杂。特别需要当地人 变量结果可能不清楚。这个变量的作用是确保该字段 在已经初始化的常见情况下,只读

延迟初始化不会假设字段可能已更改。如果这些运算符之间的字段将设置为null:

FieldType result = field;
if (result == null) { // First check (no locking)

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