在Java的內存模型保證了之前發生的對象的構造和釋放的關系:從對象的構造函數的末尾到該對象的終結器(第 12.6 節)的開頭,有一個發生之前的邊緣。以及構造函數和 final 字段的初始化:當一個對象的構造函數完成時,它被認為是完全初始化的。只有在對象完全初始化后才能看到對對象的引用的線程可以保證看到該對象的最終字段的正確初始化值。還有一個關于volatile字段的保證,因為對于這些字段的所有訪問都有一個發生在之前的關系:對 volatile 字段(第 8.3.1.4 節)的寫入發生在該字段的每次后續讀取之前。但是常規的、良好的舊非易失性字段呢?我見過很多多線程代碼,在使用非易失性字段構造對象后,它們不會打擾創建任何類型的內存屏障。但我從未見過或聽說過任何問題,我自己也無法重新創建這樣的部分結構?,F代 JVM 是否只是在構建后設置內存屏障?避免圍繞構造重新排序?還是我只是運氣好?如果是后者,是否可以編寫隨意重現部分構造的代碼?編輯:為了澄清,我正在談論以下情況。假設我們有一個類:public class Foo{ public int bar = 0; public Foo(){ this.bar = 5; } ...}一些 ThreadT1實例化一個新Foo實例:Foo myFoo = new Foo();然后將實例傳遞給其他線程,我們將調用該線程T2:Thread t = new Thread(() -> { if (myFoo.bar == 5){ .... }});t.start();T1 執行了兩個我們感興趣的寫入:T1bar將新實例化的值 5 寫入myFooT1 將新創建的對象的引用寫入myFoo變量對于 T1,我們保證寫入 #1發生在寫入 #2之前:線程中的每個操作都發生在該線程中按程序順序稍后出現的每個操作之前。但是就T2Java內存模型而言,它沒有提供這樣的保證。沒有什么可以阻止它以相反的順序查看寫入。所以它可以看到一個完全構建的Foo對象,但bar字段等于 0。編輯2:寫完幾個月后,我又看了一遍上面的例子。并且該代碼實際上可以保證正常工作,因為它T2是在T1's writes之后啟動的。這使它成為我想問的問題的不正確示例。修復它以假設 T2 在T1執行寫入時已經在運行。說T2是myFoo循環閱讀,如下所示:Foo myFoo = null;Thread t2 = new Thread(() -> { for (;;) { if (myFoo != null && myFoo.bar == 5){ ... } ... }});t2.start();myFoo = new Foo(); //The creation of Foo happens after t2 is already running
3 回答

瀟湘沐
TA貢獻1816條經驗 獲得超6個贊
但軼事證據表明這在實踐中不會發生
要查看此問題,您必須避免使用任何內存屏障。例如,如果您使用任何類型的線程安全集合或某些System.out.println
可以防止問題發生。
我之前見過這個問題,盡管我剛剛為 x64 上的 Java 8 更新 161 編寫的一個簡單測試沒有顯示這個問題。
添加回答
舉報
0/150
提交
取消