阅读Java语言规范,我发现了关于最终字段的摘录:
最终字段的使用模型很简单:设置最终字段 对于该对象的构造函数中的对象;并且不要写 引用正在另一个地方构建的对象 线程可以在对象的构造函数完成之前看到它。如果这 跟随,然后当另一个线程看到该对象时,那 线程将始终看到正确构造的版本 对象的最终字段。 它还会看到任何对象的版本或 由最终字段引用的数组,这些字段至少是最新的 最后的字段是。
链接:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5
我的问题是,"版本"意味着更新?这意味着最终字段引用的对象的非final / non-volatile字段也将在构造后从主内存(而不是本地缓存)中读取?
示例
因此,让我们说thread #1
创建一个objectB
并设置其中一个非最终/非易失性字段。
然后thread #2
将相同的字段设置为不同的字段,创建一些其他objectA
,其最终字段设置为objectB
,然后将objectA
放在{{1}的某处可以得到它。
thread #1
然后获取thread #1
,并将其最终字段视为objectA
。 objectB
是否可以看不到thread #1
对objectB
所做的更改?
或者这里有一些显示我的意思的代码:
thread #2
代码似乎读取了更新的值,但我不确定这是否仅仅是出于随机运气。
答案 0 :(得分:0)
“thread #1
是否有可能看不到objectB
对thread #2
所做的更改?”
是。因为thread #1
可以缓存值。
来自spec的引用意味着在分配最终字段值和发布对象之间存在happened before
。
答案 1 :(得分:0)
这很有趣,我想我实际上已经读过它了......这是一个例子(如果我没记错的话):
static class Holder {
private final String[] names;
static Holder newHolder;
public Holder() {
super();
names = new String[3];
names[0] = "first";
names[1] = "last";
}
public void newObject() {
newHolder = new Holder();
newHolder.names[2] = "oneMore";
}
public void readObject() {
System.out.println(Arrays.toString(newHolder.names));
}
}
假设您在此处涉及两个主题:ThreadA
和ThreadB
。现在也假设ThreadA
调用newObject
;完成后ThreadB
调用readObject
。
绝对不能保证ThreadB
会打印first, last, oneMore
;只保证first
和last
肯定会在场。
一旦你在MemoryBarriers
的{{1}}的情况下考虑final fields used inside constructor
,就可以完全理解这一点。
在当前的实现中,这个实际看起来像这样:
public Holder() {
super();
names = new String[3];
names[0] = "first";
names[1] = "last";
}
// [StoreStore]
// [LoadStore]
在构造函数的末尾插入两个障碍,阻止其他读取和存储发生。
答案 2 :(得分:0)
恕我直言,你将永远看到更新的价值..这里发生了两件事
由于我们有一个冻结动作,其他线程应该能够看到myB的内容
更多细节: https://shipilev.net/blog/2014/jmm-pragmatics/#_part_v_finals
https://www.ibm.com/developerworks/library/j-jtp03304/index.html