Java 关于在对象之前引用对象';s构造函数已完成
你们每个人都知道JMM的这个特性,有时候在这个对象的构造函数完成之前,对对象的引用可能会收到值 在最后一个字段语义中,我们还可以阅读:Java 关于在对象之前引用对象';s构造函数已完成,java,multithreading,concurrency,final,jls,Java,Multithreading,Concurrency,Final,Jls,你们每个人都知道JMM的这个特性,有时候在这个对象的构造函数完成之前,对对象的引用可能会收到值 在最后一个字段语义中,我们还可以阅读: final字段的使用模型很简单:设置final字段 对于该对象的构造函数中的对象并且不要写错误 指在另一个位置构造的对象 线程可以在对象的构造函数完成之前看到它。如果这 然后当另一个线程看到该对象时 线程将始终看到正确构造的版本 对象的最终字段(1) 紧接着在JLS中,下面的示例演示了非最终字段如何不保证初始化(1示例17.5-1.1)(2): 此外,格雷先生在
final
字段的使用模型很简单:设置final
字段
对于该对象的构造函数中的对象并且不要写错误
指在另一个位置构造的对象
线程可以在对象的构造函数完成之前看到它。如果这
然后当另一个线程看到该对象时
线程将始终看到正确构造的版本
对象的最终字段<代码>(1)
紧接着在JLS中,下面的示例演示了非最终字段如何不保证初始化(1示例17.5-1.1)(2)
:
此外,格雷先生在回答问题时写道:
如果将字段标记为final
,则构造函数保证
作为构造函数的一部分完成初始化。否则你会
在使用锁之前必须对其进行同步<代码>(3)
所以,问题是: 1) 根据语句(1),我们应该避免在对象的构造函数完成之前共享对不可变的对象的引用 2) 根据JLS给出的示例(2)和结论(3),我们似乎可以在对象的构造函数完成之前,即当其所有字段都是
final
时,安全地共享对不可变对象的引用
这不是有矛盾吗?
EDIT-1:我的确切意思。如果我们将以这种方式修改类,则该字段
y
也将是final
(2):
因此,在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
的引用
[在JLS中围绕构造函数和对象发布]是否存在一些矛盾
我认为这些问题略有不同,并不矛盾
JLS引用是关于将对象引用存储在其他线程可以在构造函数完成之前看到它的地方。例如,在构造函数中,不应将对象放入其他线程使用的静态
字段中,也不应分叉线程
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();
}
这是关键的区别 语句1)没有说明您认为它能做什么。如果有什么不同的话,我会重新表述你的说法:
1) 根据陈述(1),我们应该避免提及
不可变对象在其构造函数完成之前
阅读
1) 根据陈述(1),我们应该避免提及
可变对象在其构造函数完成之前
其中,我所说的可变是指具有任何非最终字段或对可变对象的最终引用的对象。(必须承认,我不是100%地认为您需要担心可变对象的最终引用,但我认为我是对的…)
换言之,您应该区分:
- 最终字段(可能不可变对象的不可变部分)
- 在任何人与此对象交互之前必须初始化的非最终字段
- 在任何人与此对象交互之前不必初始化的非最终字段
final
),但对于具有非final
字段的对象,您需要谨慎使用,这些字段必须在对象被任何人使用之前进行初始化
换句话说,对于您发布的已编辑JLS示例,其中两个字段都是final
,int j=f.y代码>保证为最终版本。但这意味着不需要避免写入对对象f的引用,因为在任何人看到它之前,它总是处于正确初始化的状态。您不必担心它,JVM会这样做。在完整示例中
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
}
}
}
如您所见,f
直到构造函数返回后才设置。这意味着f.x
是安全的,因为它是final
,构造函数已返回
在下面的示例中,两个值都不保证设置
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
f = this; // assign before finished.
}
static void writer() {
new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // not guaranteed to see 3
int j = f.y; // could see 0
}
}
}
根据语句(1),我们应该避免在不可变对象的构造函数完成之前共享对该对象的引用
在基于多种原因(不可变或其他原因)构造对象转义之前,不应允许引用对象转义,例如,在存储对象后,对象可能会引发异常
根据JLS给出的示例(2)和结论(3),我们似乎可以安全地共享对不可变对象的引用,即当其所有字段都是最终字段时
构建对象后,可以在线程之间安全地共享对不可变对象的引用
注
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
}
}
}
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
f = this; // assign before finished.
}
static void writer() {
new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // not guaranteed to see 3
int j = f.y; // could see 0
}
}
}
1 constructor enter{
2 assign final field
3 publish this
4 }constructor exit
5 publish the newly constructed object