如果您将对象分配给最终字段,其他线程是否会看到该对象的非最终/非易失性字段的先前更新?
读Java语言规范,我发现这个摘录有关最终字段:如果您将对象分配给最终字段,其他线程是否会看到该对象的非最终/非易失性字段的先前更新?
最终场的使用模式很简单:将最终场 在该对象的构造方法的对象;并且不要在对象的构造函数完成之前,在线程可以看到它的地方写入 引用。如果遵循这个 ,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。 它还会看到任何对象或 数组引用的版本,这些最终字段至少为最新的 ,因为最终字段为。
链接:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5
我的问题是,做 “版本” 意味着更新?这意味着在构造之后,最终字段引用的对象的非最终/非易失性字段也将从主内存(不是本地缓存)中读取?
例
所以我们说thread #1
创建objectB
并设置其非final /非易失性的领域之一。
然后thread #2
集,同一领域不同的东西,造成其他一些objectA
设定为objectB
最后的字段,然后把那objectA
的地方在那里thread #1
可以得到它。
thread #1
然后获得objectA
,并将其最终字段视为objectB
。 thread #1
有没有可能看不到由thread #2
所做的更改为objectB
?
或者在这里表示我的意思了一些代码:
public class Test {
private static final ConcurrentLinkedQueue<A> myAs = new ConcurrentLinkedQueue<>();
private static long timer = System.nanoTime() + 3000000000L; // 3 seconds into the future
public static void main(String... args) {
B myB = new B("thread #1"); // Set in thread 1
new Thread(() -> {
myB.setString("thread #2"); // Set in thread 2
myAs.add(new A(myB));
}).start();
for(long i = 0; i < x; i = System.nanoTime()) {} // Busy-wait for about 3 seconds
System.out.println(myAs.poll().getB().getString()); // Print out value
}
public static class A {
private final B b;
public A(B b) {
this.b = b;
}
public B getB() {
return b;
}
}
public static class B {
private String s = null;
public B(String s) {
this.s = s;
}
public String getString() {
return s;
}
public void setString(String s) {
this.s = s;
}
}
}
代码似乎读取更新的价值,但我不知道这是刚走出随机运气。
恕我直言,你总是会看到更新后的值。有两件事情被这里发生
- 安全出版物 在构造结束
- 冻结行动
因为我们有其他线程的冻结动作应该能够看到myB的内容
更多详情: https://shipilev.net/blog/2014/jmm-pragmatics/#_part_v_finals
https://www.ibm.com/developerworks/library/j-jtp03304/index.html
“thread #1
可能不会看到由thread #2
所做的更改为objectB
?”
是的。因为thread #1
可以缓存值。
来自spec的引用意味着在最终字段值的赋值和对象的发布之间有happened before
。
这很有趣,我想我实际上阅读了前一阵子...下面是一个例子(如果我没有记错的例子):
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
在final fields used inside constructor
的情况下使用,这是完全可以理解的。
在目前的执行,这实际上看起来是这样的:
public Holder() {
super();
names = new String[3];
names[0] = "first";
names[1] = "last";
}
// [StoreStore]
// [LoadStore]
有被插在阻止其他读取和存储的情况发生constrcutor的末尾有两个障碍。
很适合第一个问题。 – lexicore