为什么要安全地发布对象,为什么需要“存储对最终字段的引用”和“正确构造的对象”?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么要安全地发布对象,为什么需要“存储对最终字段的引用”和“正确构造的对象”?相关的知识,希望对你有一定的参考价值。
我正在阅读“实践中的Java并发”,它说:
要安全地发布对象,必须同时使对象的引用和对象的状态对其他线程可见。正确构造的对象可以通过以下方式安全发布:
- 从静态初始化程序初始化对象引用;
- 将对它的引用存储到volatile字段或AtomicReference中;
- 将对它的引用存储到正确构造的对象的最终字段中;要么
- 将对它的引用存储到由锁正确保护的字段中。
我不明白的是“将它的引用存储到正确构造的对象的final
字段中”,为什么需要“正确构造的对象”?如果没有“正确构造的对象”,其他线程是否可以看到以不一致状态发布的对象?
我读过几个相关的问题:
- final vs volatile guaranntee w.rt to safe publication of objects - Stack Overflow
- safe publication of mutable object - Stack Overflow
但是我找不到很多解释为什么需要“正确构造的物体”。
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
的状态必须做同时对线程可见“。所以,field
安全发布,即使SomeClass
没有正确构建。我对吗?
希望有人能提前给我更多解释,谢谢。
这取决于你所谓的“一致状态”。如果看到一个空指针应该是一个已发布的对象,即该对象实际上看起来好像它没有被发布,则计为“一致”,那么你就是正确的,因为该示例产生了“一致性”。
请注意,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
对象恰好包含该字段。
以上是关于为什么要安全地发布对象,为什么需要“存储对最终字段的引用”和“正确构造的对象”?的主要内容,如果未能解决你的问题,请参考以下文章