考虑“实例字段的延迟初始化的双重检查习语”:
// 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。
答案 0 :(得分:4)
是的,这是线程安全的。
synchronized块用于防止多个线程不必要地调用computeFieldValue()
。由于field
是易变的,reset
和getField
中的访问都是有序的。
如果第一次检查非空,则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()提供的结果不正确。