我试着理解最后一个领域的语义。
让研究代码:
public class App {
final int[] data;
static App instance;
public App() {
this.data = new int[]{1, 0};
this.data[1] = 2;
}
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
instance = new App();
}
}).start();
while (instance == null) {/*NOP*/}
System.out.println(Arrays.toString(instance.data));
}
}我有一些问题:
P.S.我不知道如何使标题正确,请随时编辑。
附加内容
如果我们替换:
public App() {
this.data = new int[]{1, 0};
this.data[1] = 2;
}使用
public App() {
int [] data = new int[]{1, 0};
data[1] = 2;
this.data = data;
}另外,我想知道在我的示例中,如果用final替换易失性,wjat将是什么样子。
因此,我想得到关于4个新案例的解释。
发布于 2017-02-01 15:14:41
是的,带着渔获物。您将在循环之后重新读取instance变量,而且由于这两种读取都是动态的,退出循环并不能保证循环后读取非null引用。
由于这个问题不是问题的主题,请进行以下更改:
App instance;
while((instance=App.instance) == null) {/*NOP*/}
System.out.println(Arrays.toString(instance.data));然后,如果应用程序终止,它将输出[1,2]。问题是,final字段语义适用于整个构造函数,数组引用写入该字段的确切时间与此无关。这还意味着,在构造函数中,重新排序是可能的,因此,如果this引用在构造函数完成之前转义,则所有保证都是无效的,无论this是在程序顺序写入之前还是之后转义。因为在您的代码中,this不会在构造函数完成之前转义,所以应用了保证。
请参阅字段语义
当构造函数完成时,对象被认为是完全初始化的。只有在对象被完全初始化后才能看到对对象的引用的线程,保证看到该对象的
final字段的正确初始化值。
注意,它引用的是完全初始化的状态,而不是对特定final字段的写入。这一点也将在下一节第17.5.1节中讨论。
设o是对象,c是o的构造函数,其中写入了
final字段f。当c正常或突然退出时,o的final字段f会发生冻结作用。
如果将变量更改为volatile,则几乎没有任何保证。volatile字段建立了对该变量的写入与后续读取之间的关系之前发生的情况,但经常被忽略的关键点是“后续”一词。如果App实例发布不当,如您的示例中所示,则无法保证主线程对instance.data的读取将是后续的。如果它读取null引用(这现在是可能的),那么您就知道它不是后续的。如果它读取一个非null引用,您知道它是字段写入之后的,这意味着您保证在第一个槽中读取1,但是在第二个槽中,您可以读取0或2。
如果您想从障碍和重新排序的角度来讨论这一点,volatile写到data保证所有以前的写入都已提交,这包括将1写入到第一个数组槽,但它不能保证后续的非volatile写入不会提前提交。因此,仍然有可能在App写入之前执行不适当的volatile引用发布(尽管这种情况很少发生)。
如果将写入移至构造函数的末尾,则一旦看到非null数组引用,所有以前的写入都是可见的。对于final字段,不需要进一步讨论,如上所述,构造函数中写入的实际位置与此无关。对于volatile情况,如前所述,不能保证读取非null引用,但是当您读取它时,所有以前的写入都已提交。知道new int[]{1, 0};表达式被编译成相当于hiddenVariable=new int[2]; hiddenVariable[0]=1; hiddenVariable[1]=0;的表达式可能会有所帮助。将另一个数组写入放在其构造之后,但在数组引用到字段的volatile写入之前,不会更改语义。
https://stackoverflow.com/questions/41955348
复制相似问题