使用双重检查惯用法重置延迟加载的字段
考虑“实例字段延迟初始化的双重检查习惯用法”:
// 从 对布洛赫的采访。 私有易失性 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
是的,这是线程安全的。
同步块是为了防止多个线程不必要地调用computeFieldValue()。 由于
field
是易失性的,因此reset
和getField
中的访问都是有序的。如果第一次检查非空,则完成 getField ; 返回
结果
。否则,将获取锁,排除可能将字段设置为非空的任何其他线程,但允许任何线程将
field
设置为空。 如果任何线程确实将field
设置为 null,则不应发生任何更改; 这就是使线程进入同步块的条件。 如果另一个线程在当前线程检查后已经获取了锁,并将该字段设置为非空值,则第二次检查将检测到这一点。Yes, this is thread safe.
The synchronized block is to prevent multiple threads from unnecessarily calling
computeFieldValue()
. Sincefield
is volatile, the accesses inreset
andgetField
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 setfield
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.我认为这应该是安全的,但这只是因为您将该字段存储在局部变量中。 完成此操作后,即使另一个线程正在中途重置字段的值,局部变量引用也无法神奇地更改为 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.
看起来只要重置方法是上面列出的
reset()
方法就可以工作。 但是,如果reset()
方法实例化一个新对象(如下所示),您最终是否可能会返回与预期不同的内容?It seems like this will work as long as the reset method is the
reset()
method listed above. However, if thereset()
method instantiates a new Object (like below), couldn't you end up potentially returning something different than you intended?我想这取决于你所说的线程安全到底是什么意思。
您最终可能会遇到这样的情况:在第二个实例之后使用第一个实例。 这可能没问题,也可能没问题。
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.
我认为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:
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.