我正在阅读“实践中的Java并发性”,它说:
要安全地发布对象,必须同时使对该对象的引用和该对象的状态对其他线程可见。可以通过以下方式安全地发布正确构造的对象:
- 从静态初始值设定项初始化对象引用;
- 将对它的引用存储到volatile字段或AtomicReference中;
- 将对它的引用存储到适当构造的对象的最终字段中;或
- 将对其的引用存储到由锁适当保护的字段中。
我不理解的是“将对其的引用存储到正确构建的对象的final
字段中”,为什么需要“正确构建的对象”? < strong>没有“没有适当构造的对象”,其他线程可以看到处于不一致状态的对象吗?
我已经阅读了几个相关的问题:
但是我找不到为什么需要“适当构造的对象”的很多解释。
class SomeClass{
private final SomeType field;
SomeClass() {
new Thread(new Runnable() {
public void run() {
SomeType copy = field; //copy could be null
copy.doSomething(); //could throw NullPointerException
}
}).start();
field = new SomeType();
}
}
SomeClass
的构建方式不正确,当然copy
可能是null
,但是我认为该线程无法看到copy
不一致的状态,copy
是null
或“对copy
的引用,并且copy
的状态必须同时显示给线程可见” < / strong>。因此,即使field
的构造不正确,也可以安全发布SomeClass
。我说的对吗?
希望有人可以给我更多的解释,谢谢。
答案 0 :(得分:0)
这取决于您所说的“一致状态”。如果看到一个空指针应位于已发布对象的位置,即该对象实际上看起来好像未发布,则算作“一致”,那么您是对的,因为该示例会产生“一致”。
但是请注意,final
个字段应该不更改其值。如果线程从final
读取,则可以安全地假定该字段的值以后不会更改。线程的实现或(JIT)编译器可能会将字段的值“缓存”在某个变量(或寄存器)中,因为final
告诉它读取的值保持不变。
具体来说,类似
的代码new Thread(new Runnable() {
public void run() {
while ( field == null ) {
// Wait a little...
}
// Field was initialized, go ahead.
}
}).start();
可能只有两种可能的结果:要么永远不会进入循环,要么变成无限循环。
这就是为什么在初始化之前先访问final
字段 的不安全的原因;和final
字段只能保证在构造函数完成后 初始化。
如果您在线程的代码中写入标准字段名SomeClass.this.field
,则问题可能会变得更加明显。您可以忽略它,但是编译器将为您隐式生成正确的访问权限。使用完全限定的字段名称,您可以更清楚地看到线程在SomeClass.this.field
完全初始化之前访问了this
。因此,实际上,您实际上并没有发布一致的SomeType
对象,而是发布了仍然包含该字段的仍然不一致的SomeClass
对象。