[英]Java reordering and volatile issue
我最近遇到了一個有趣的問題:
例如我有A類:
class A {
int a;
int b;
public A() {
a = 1;
b = 2;
}
public int getA() {return a;}
public int getB() {return b;}
}
A類必須僅作為單身存在; 所以,為了提供對單例的訪問,我創建了類Factory:
class Factory {
private A a;
public A getA() {
if (a == null) {
synchronized(this) {
if (a == null) {
a = new A();
}
}
}
return a;
}
}
據我了解,根據JVM中的重新排序,如果2個線程同時訪問Factory.getA(),則其中一個線程可能會獲得部分構造的對象,並且可能導致應用程序崩潰。
但如果我私有A a; 我可以確定每個線程只能訪問完全構造的對象嗎?
因此,作為結論,如果我將變量x標記為volatile,它是否會影響x.class構造函數的內容?
但如果我私有A a; 我可以確定每個線程只能訪問完全構造的對象嗎?
是。
考慮線程#1初始化a
。 工廠互斥鎖確保一次只有一個線程可以創建A
所以我們有以下順序:
線程#1看到a
為空
線程#1獲取互斥鎖。
線程#1看到a
仍為null。
線程#1構造一個A
實例,初始化其變量。
線程#1將實例引用分配給a
。
線程#1釋放鎖定。
一個線程#2看到a
在非空狀態(例如,在任一步驟#1或步驟#3的測試),這意味着,線程#1必須已經變得到(至少)步驟#5。 但這意味着
A.a = 1; // thread #1
“之前發生”
A.b = 2; // thread #1
“發生在之前”
write to Factory.a // thread #1
“發生在之前”
read from Factory.a // thread #2
“發生在之前”
access to some field of `A` // thread #2
因為從線程#1中的新A
的字段初始化到線程#2中的字段訪問,存在完整的“發生在之前”關系鏈。 這意味着線程#2 保證看到字段初始化寫入的結果......除非對這些字段有其他干預寫入。
請注意,它是寫入volatile a
之間的“發生之前”,而后續讀取是另一個作為鍵的線程。 如果a
不是易失性的,那么就沒有完整的“先發生過”鏈, 因此執行不是“發生 - 在一致之前”; 即它包含“數據競賽”。
我上面使用/遵循的術語和推理來自Java語言規范, 第17.4章 。
揮發物
Java volatile關鍵字用於將Java變量標記為“存儲在主存儲器中”。 更確切地說,這意味着,每次讀取一個volatile變量都將從計算機的主內存中讀取,而不是從CPU緩存中讀取,並且每次寫入volatile變量都將寫入主內存,而不僅僅是CPU緩存。
它與記憶有關。 它不會影響構造函數。
來源:點擊這里
既然你想讓A成為一個單身人士,我的建議就是讓A成為一個枚舉。
使用枚舉:
並且使用枚舉是實現單例模式的最佳方式。
enum A{ INSTANCE(1,2); private int a; private int b; A(int a,int b){ this.a=a; this.b=b; } public int getA(){ return a; } public int getB(){ return b; } }
規范和實現有兩種不同的東西。 如果你制作a
volatile,那將解決實際問題,因為JVM會在賦值后插入一個商店障礙。 但是這仍然會破壞規范,因為volatile
不是final
,它不能保證會發布一個對象的所有子對象。 順便說一句,在具有強大內存模型的CPU上,很多不良代碼都可以正常工作,但這並不意味着你應該這樣寫。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.