使用双重检查惯用法重置延迟加载的字段

发布于 2024-07-08 13:16:43 字数 850 浏览 10 评论 0原文

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

// 从 对布洛赫的采访。 
  私有易失性 FieldType 字段; 
  字段类型 getField() { 
      字段类型结果=字段; 
      if (result == null) { // 首先检查(无锁定) 
          同步(这个){ 
              结果=字段; 
              if (result == null) // 第二次检查(带锁定) 
                  字段=结果=computeFieldValue(); 
          } 
      } 
       返回结果; 
  }

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

void reset() { 
     字段=空; 
  }

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

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

Consider the "double-check idiom for lazy initialization of instance fields":

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

I want to be able to reset the field in a safe way (force it to load again from the database, in my case). I assume that we could do this by having a reset method:

void reset() {
   field = null;
}

Is this the standard way of doing resetting the field? Is it safe? Any pitfalls? I'm asking because Bloch gave the following warning about double-checked lazy-loading: "The idiom is very fast but also complicated and delicate, so don't be tempted to modify it in any way. Just copy and paste -- normally not a good idea, but appropriate here."

Thanks in advance,
Playa from the Himalayas.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

櫻之舞 2024-07-15 13:16:43

是的,这是线程安全的。

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

如果第一次检查非空,则完成 getField ; 返回结果

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

Yes, this is thread safe.

The synchronized block is to prevent multiple threads from unnecessarily calling computeFieldValue(). Since field is volatile, the accesses in reset and getField are all well-ordered.

If the first check is non-null, getField is done; result is returned.

Otherwise, a lock is acquired, excluding any other thread that might set the field to non-null, but permitting any thread to set field to null. If any thread does set field to null, nothing should have changed; that's the condition that got the thread into the synchronized block. If another thread had already acquired the lock after the current thread's check, and set the field to a non-null value, the second check will detect that.

中性美 2024-07-15 13:16:43

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

I think this should be safe, but only because you're storing the field in a local variable. After this is done, there's no way for the local variable reference to magically change to null, even if another thread is resetting field's value half-way through.

別甾虛僞 2024-07-15 13:16:43

看起来只要重置方法是上面列出的 reset() 方法就可以工作。 但是,如果 reset() 方法实例化一个新对象(如下所示),您最终是否可能会返回与预期不同的内容?

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

It seems like this will work as long as the reset method is the reset() method listed above. However, if the reset() method instantiates a new Object (like below), couldn't you end up potentially returning something different than you intended?

void reset() {
    field = new FieldType();
}
半世晨晓 2024-07-15 13:16:43

我想这取决于你所说的线程安全到底是什么意思。

您最终可能会遇到这样的情况:在第二个实例之后使用第一个实例。 这可能没问题,也可能没问题。

I guess it depends on exactly what you mean by thread-safe.

You could end up with a situation where a first instance is used after a second. That may be okay, or it may not.

对不⑦ 2024-07-15 13:16:43

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

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

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

FieldType result = field;
if (result == null) { // 首先检查(无锁定)

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

I think reset() method isn't correct. If you read Item 71, you'll find:

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.

Lazy initialization does't suppose that field might changed. If field will be set to null between these operators:

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

getField() provide incorrect result.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文