2017-10-09 88 views
7

读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,并将其最终字段视为objectBthread #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; 
     } 
    } 
} 

代码似乎读取更新的价值,但我不知道这是刚走出随机运气。

+3

很适合第一个问题。 – lexicore

回答

1

thread #1可能不会看到由thread #2所做的更改为objectB?”

是的。因为thread #1可以缓存值。

来自spec的引用意味着在最终字段值的赋值和对象的发布之间有happened before

1

这很有趣,我想我实际上阅读了前一阵子...下面是一个例子(如果我没有记错的例子):

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肯定会存在。

这个顺便说一句,如果你认为MemoryBarriersfinal fields used inside constructor的情况下使用,这是完全可以理解的。

在目前的执行,这实际上看起来是这样的:

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

有被插在阻止其他读取和存储的情况发生constrcutor的末尾有两个障碍。

相关问题