5

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

// Effective Java 中的第 71 条复制自这次对 Bloch 的采访。
私有易失性 FieldType 字段;
字段类型 getField() {
    字段类型结果 = 字段;
    if (result == null) { // 第一次检查(无锁定)
        同步(这个){
            结果=字段;
            if (result == null) // 第二次检查(带锁)
                字段 = 结果 = 计算字段值();
        }
    }
     返回结果;
}

我希望能够以安全的方式重置该字段(在我的情况下,强制它从数据库中再次加载)。我假设我们可以通过使用重置方法来做到这一点:

无效重置(){
   字段=空;
}

这是重置字段的标准方法吗?安全吗?有什么陷阱吗?我之所以问,是因为 Bloch 就双重检查延迟加载给出了以下警告:“这个习语非常快,但也很复杂和微妙,所以不要试图以任何方式修改它。只需复制和粘贴——通常不是一个好主意,但在这里很合适。”

提前感谢来自喜马拉雅山的普拉亚。

4

5 回答 5

4

是的,这是线程安全的。

同步块是为了防止多个线程不必要地调用computeFieldValue(). 由于field是 volatile 的,因此reset和中的访问getField都是有序的。

如果第一次检查不为空,getField则完成;result被退回。

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

于 2008-11-20T23:01:17.490 回答
3

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

于 2008-11-20T22:37:28.800 回答
0

只要重置方法是reset()上面列出的方法,这似乎就可以工作。但是,如果该reset()方法实例化了一个新对象(如下所示),您最终会不会返回与您预期不同的东西?

void reset() {
    field = new FieldType();
}
于 2008-11-20T22:55:53.413 回答
0

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

您最终可能会遇到一种情况,即在一秒钟后使用第一个实例。这可能没问题,也可能不行。

于 2008-11-26T13:06:45.237 回答
0

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

这段代码可能看起来有点复杂。特别是,对局部变量结果的需求可能不清楚。这个变量的作用是确保该字段 在已经初始化的常见情况下只读取一次。

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

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

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

于 2013-01-23T10:41:31.953 回答