如果将对象分配给最终字段,其他线程是否会看到该对象的非最终/非易失性字段的先前更新?

时间:2017-10-09 06:10:18

标签: java multithreading concurrency thread-safety volatile

阅读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,并将其最终字段视为objectAobjectB是否可以看不到thread #1objectB所做的更改?

或者这里有一些显示我的意思的代码:

thread #2

代码似乎读取了更新的值,但我不确定这是否仅仅是出于随机运气。

3 个答案:

答案 0 :(得分:0)

thread #1是否有可能看不到objectBthread #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));
    }

}

假设您在此处涉及两个主题:ThreadAThreadB。现在也假设ThreadA调用newObject;完成后ThreadB调用readObject

绝对不能保证ThreadB会打印first, last, oneMore;只保证firstlast肯定会在场。

一旦你在MemoryBarriers的{​​{1}}的情况下考虑final fields used inside constructor,就可以完全理解这一点。

在当前的实现中,这个实际看起来像这样:

public Holder() {
    super();
    names = new String[3];
    names[0] = "first";
    names[1] = "last";
}
// [StoreStore]
// [LoadStore]

在构造函数的末尾插入两个障碍,阻止其他读取和存储发生。

答案 2 :(得分:0)

恕我直言,你将永远看到更新的价值..这里发生了两件事

  1. 安全发布
  2. 在构造函数结束时冻结操作
  3. 由于我们有一个冻结动作,其他线程应该能够看到myB的内容

    更多细节: https://shipilev.net/blog/2014/jmm-pragmatics/#_part_v_finals

    https://www.ibm.com/developerworks/library/j-jtp03304/index.html