【发布时间】:2013-02-01 19:38:28
【问题描述】:
大家都知道JMM的这个特性,有时对对象的引用可以在这个对象的构造函数完成之前接收到值。
在JLS7, p. 17.5 final Field Semantics我们也可以读到:
final字段的使用模型很简单:设置final字段 对于该对象的构造函数中的对象; 并且不要写 对在另一个地方构造的对象的引用 线程可以在对象的构造函数完成之前看到它。如果这 紧随其后,然后当另一个线程看到该对象时, 线程将始终看到正确构造的版本 对象的final字段。(1)
紧接着在 JLS 中的示例如下,它演示了如何不保证初始化 non-final 字段 (1Example 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
}
}
}
另外,格雷先生在this问答中写道:
如果您将该字段标记为
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 的所有字段都是final 时,为什么我们应该避免在对象f 的构造函数完成之前(根据(1))写入对它的引用?
【问题讨论】:
标签: java multithreading concurrency final jls