簡體   English   中英

如果將對象分配給最終字段,其他線程是否會看到該對象的非最終/非易失性字段的先前更新?

[英]If you assign an Object to a final field, will other threads see previous updates of that Object's non-final/non-volatile fields?

閱讀Java語言規范,我發現了關於最終字段的摘錄:

final字段的用法模型很簡單:在該對象的構造函數中設置對象的最終字段; 並且在對象的構造函數完成之前,不要在另一個線程可以看到的地方寫入對正在構造的對象的引用。 如果遵循此原因,那么當另一個線程看到該對象時,該線程將始終看到該對象的最終字段的正確構造版本。 它還將看到那些最終字段引用的任何對象或數組的版本,這些字段至少與最終字段一樣是最新的

鏈接: https//docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

我的問題是,“版本”是否意味着更新? 這意味着最終字段引用的對象的非final / non-volatile字段也將在構造后從主內存(而不是本地緩存)中讀取?


所以我們假設thread #1創建了一個objectB並設置了一個非final / non-volatile字段。

然后thread #2集,同一領域不同的東西,創造一些其他的objectA設定為最后一個字段objectB ,然后把該objectA的地方在那里thread #1可以得到它。

thread #1然后獲取objectA ,並將其最終字段視為objectB thread #1是否有可能看不到thread #2objectB所做的更改?

或者這里有一些顯示我的意思的代碼:

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;
        }
    }
}

代碼似乎讀取了更新的值,但我不確定這是否僅僅是出於隨機運氣。

thread #1是否有可能看不到thread #2objectB所做的更改?”

是。 因為thread #1可以緩存值。

來自spec的引用意味着happened before最終字段值的分配和對象的發布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));
    }

}

假設這里涉及兩個線程: ThreadAThreadB 現在還假設ThreadA調用newObject ; 當它完成時, ThreadB調用readObject

絕對不能保證ThreadBfirst, last, oneMore打印first, last, oneMore打印first, last, oneMore ; 只保證firstlast肯定會出現。

一旦你想到在final fields used inside constructor中使用的final fields used inside constructor情況下使用的MemoryBarriers ,這個btw是完全可以理解的。

在當前的實現下,這實際上是這樣的:

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

在構造函數的末尾插入兩個障礙,阻止其他讀取和存儲發生。

恕我直言,你將永遠看到更新的價值..這里發生了兩件事

  1. 安全出版
  2. 在構造函數的末尾凍結動作

由於我們有一個凍結動作,其他線程應該能夠看到myB的內容

更多細節: https//shipilev.net/blog/2014/jmm-pragmatics/#_part_v_finals

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM