你们每个人都知道JMM的这个特性,有时对对象的引用可以在该对象的构造函数完成之前接收值。
在JLS7 中,p。17.5 final 字段语义我们还可以阅读:
字段的使用模型
final
很简单:final
在对象的构造函数中设置对象的字段;并且不要在对象的构造函数完成之前在另一个线程可以看到它的地方写对正在构造的对象的引用。如果遵循这一点,那么当另一个线程看到该对象时,该线程将始终看到该对象final
字段的正确构造版本。(1)
紧随其后的是 JLS 中的示例,该示例演示了如何不保证初始化 非最终(2)
字段(1Example 17.5-1.1) :
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
此外,在这个问答中,格雷先生写道:
如果将字段标记为,
final
则保证构造函数作为构造函数的一部分完成初始化。否则,您必须在使用锁之前对其进行同步。(3)
所以,问题是:
1)根据语句(1)我们应该避免在构造函数完成之前共享对不可变对象的引用
2) 根据 JLS 给出的示例 (2) 和结论 (3),我们似乎可以在其构造函数完成之前安全地共享对不可变对象的引用,即当它的所有字段都是.final
是不是有些矛盾?
EDIT-1:我的意思是什么。如果我们以这种方式修改示例中的类,该字段y
也将是final
(2):
class FinalFieldExample {
final int x;
final int y;
...
因此在reader()
方法中可以保证:
if (f != null) {
int i = f.x; // guaranteed to see 3
int j = f.y; // guaranteed to see 4, isn't it???
如果是这样,为什么我们应该避免在它的构造函数完成之前写入对对象的引用f
(根据(1)),当所有字段f
都是最终的?