关于在对象的构造函数完成之前对对象的引用
问题内容:
你们每个人都知道 JMM的 这一功能,有时对对象的引用可能 在 此对象的构造函数完成 之前 获得值。
在JLS7中,第4页。17.5
最终的字段语义
我们还可以阅读:
final
字段的使用模型很简单:final
在对象的构造函数中设置对象的字段;
并且不要在对象的构造函数完成之前,在另一个线程可以看到它的地方编写对正在构造的对象的引用
。如果执行此操作,则当另一个线程看到该对象时,该线程将始终看到该对象的final
字段的正确构造版本。(1)
在紧接着的JLS中,下面的示例演示了如何确保 不最终 字段的初始化(示例17.5-1.1) (2)
:
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
当对象的所有字段f
都是final 时,为什么要避免在对象的构造函数完成之前编写对对象的引用(根据(1))?
问题答案:
(在有关构造函数和对象发布的JLS中)没有矛盾吗?
我相信这些是不矛盾的稍微不同的问题。
JLS引用是关于将对象引用存储在构造函数完成之前 其他线程
可以看到它的地方。例如,在构造函数中,您不应将对象放入static
其他线程使用的字段中,也不应分叉线程。
public class FinalFieldExample {
public FinalFieldExample() {
...
// very bad idea because the constructor may not have finished
FinalFieldExample.f = this;
...
}
}
您也不应该在构造函数中启动线程:
// obviously we should implement Runnable here
public class MyThread extends Thread {
public MyThread() {
...
// very bad idea because the constructor may not have finished
this.start();
}
}
即使您所有的字段都final
在一个类中,在构造函数完成之前将对象的引用共享给另一个线程也不能保证在其他线程开始使用该对象时已经设置了这些字段。
我的答案是关于在构造函数完成后使用不同步的对象。尽管在构造函数,缺乏同步性以及编译器对操作的重新排序方面相似,但这是一个略有不同的问题。
在JLS 17.5-1中,它们 不在 构造函数内部分配静态字段。他们用另一种静态方法分配静态字段:
static void writer() {
f = new FinalFieldExample();
}
这是关键的区别。