简体   繁体   English

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

[英]Resetting a field lazy-loaded with the double-check idiom

Consider the "double-check idiom for lazy initialization of instance fields": 考虑“对实例字段的延迟初始化进行双重检查”:

 // Item 71 in Effective Java copied from this interview with Bloch . //在Bloch的采访中复制了Effective Java中的第71项。\nprivate volatile FieldType field; private volatile FieldType字段;\nFieldType getField() { FieldType getField(){\n    FieldType result = field; FieldType结果=字段;\n    if (result == null) { // First check (no locking) if(result == null){//首先检查(无锁定)\n        synchronized(this) { synchronized(this){\n            result = field; result = field;\n            if (result == null) // Second check (with locking) if(result == null)//第二次检查(带锁定)\n                field = result = computeFieldValue(); field = result = computeFieldValue();\n        } }\n    } }\n     return result; 返回结果;\n} } 

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: 我假设我们可以通过重置方法来做到这一点:

\nvoid reset() { void reset(){\n   field = null; field = null;\n} } 

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. 在此先感谢喜马拉雅山脉的Playa。

Yes, this is thread safe. 是的,这是线程安全的。

The synchronized block is to prevent multiple threads from unnecessarily calling computeFieldValue() . synchronized块用于防止多个线程不必要地调用computeFieldValue() Since field is volatile, the accesses in reset and getField are all well-ordered. 由于field是易失性的,因此resetgetField中的访问都是有序的。

If the first check is non-null, getField is done; 如果第一次检查是非null,则getField完成; result is returned. 返回result

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. 否则,获取锁,排除可能将该字段设置为非null的任何其他线程,但允许任何线程将field设置为null。 If any thread does set field to null, nothing should have changed; 如果任何线程将field设置为null,则不应该更改任何内容; 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. 如果另一个线程在当前线程检查后已经获取了锁,并将该字段设置为非空值,则第二个检查将检测到该值。

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. 完成此操作后,即使另一个线程正在重置字段的值,也无法将局部变量引用神奇地更改为null。

I think reset() method isn't correct. 我认为reset()方法不正确。 If you read Item 71, you'll find: 如果你阅读第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. 这个变量的作用是确保该字段在已经初始化的常见情况下只读一次。

Lazy initialization does't suppose that field might changed. 延迟初始化不会假设该字段可能已更改。 If field will be set to null between these operators: 如果这些运算符之间的字段将设置为null:

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

getField() provide incorrect result. getField()提供不正确的结果。

It seems like this will work as long as the reset method is the reset() method listed above. 只要reset方法是上面列出的reset()方法,这似乎就可以工作。 However, if the reset() method instantiates a new Object (like below), couldn't you end up potentially returning something different than you intended? 但是,如果reset()方法实例化一个新的Object(如下所示),你最终是否可能返回与你想要的不同的东西?

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

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. 这可能没问题,也可能没有。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM