[英]Visibility effects of synchronization in Java
這篇文章說:
在這個不兼容的代碼示例中,通過將其字段聲明為final來使Helper類不可變。 JMM保證不可變對象在任何其他線程可見之前就已完全構建。 getHelper()方法中的塊同步可確保所有可以看到helper字段非空值的線程也將看到完全初始化的Helper對象。
public final class Helper {
private final int n;
public Helper(int n) {
this.n = n;
}
// Other fields and methods, all fields are final
}
final class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) { // First read of helper
synchronized (this) {
if (helper == null) { // Second read of helper
helper = new Helper(42);
}
}
}
return helper; // Third read of helper
}
}
但是,不能保證此代碼在所有Java虛擬機平台上都能成功,因為在輔助程序的第一次讀取和第三次讀取之間沒有之前發生的關系。 因此,對輔助程序的第三次讀取有可能獲得陳舊的空值(可能是因為其值已由編譯器緩存或重新排序),從而導致getHelper()方法返回空指針。
我不知道該怎么做。 我可以同意,在第一讀和第三讀之間沒有任何關系,至少沒有直接的關系。 在某種意義上說,第一讀必須在第二之前發生,並且第二讀必須在第三之前發生,因此第一讀必須在第三之前發生
有人可以更熟練地進行詳細說明嗎?
不,沒有傳遞關系。
JMM背后的想法是定義JVM必須遵守的規則。 只要JVM遵循這些規則,就可以根據需要授權它們重新排序和執行代碼。
在您的示例中,第2次讀取和第3次讀取不相關-例如,通過使用synchronized
或volatile
不會引入任何存儲障礙。 因此,允許JVM執行以下操作:
public Helper getHelper() {
final Helper toReturn = helper; // "3rd" read, reading null
if (helper == null) { // First read of helper
synchronized (this) {
if (helper == null) { // Second read of helper
helper = new Helper(42);
}
}
}
return toReturn; // Returning null
}
然后,您的呼叫將返回空值。 但是,將創建一個單例值。 但是,后續調用仍可能會得到一個空值。
如建議的那樣,使用易失性會引入新的存儲障礙。 另一個常見的解決方案是捕獲讀取的值並將其返回。
public Helper getHelper() {
Helper singleton = helper;
if (singleton == null) {
synchronized (this) {
singleton = helper;
if (singleton == null) {
singleton = new Helper(42);
helper = singleton;
}
}
}
return singleton;
}
由於您依賴局部變量,因此無需重新排序。 一切都在同一線程中發生。
不,這些讀段之間沒有任何傳遞關系。 synchornized
僅保證可見同一鎖的同步塊中所做的更改。 在這種情況下,所有讀取都不會在同一鎖上使用同步塊,因此存在缺陷,並且無法保證可見性。
因為一旦字段初始化就沒有鎖定,所以將字段聲明為volatile
至關重要。 這樣可以確保可見性。
private volatile Helper helper = null;
一切在這里https://shipilev.net/blog/2014/safe-public-construction/#_singletons_and_singleton_factories進行了解釋,這個問題很簡單。
...請注意,我們在這段代碼中對實例進行了多次讀取,並且至少“ read 1”和“ read 3”是沒有任何同步的讀取...在規范方面,如發生在一致性規則之前的那樣,讀取操作可以通過種族觀察無序寫入。 這是針對每個讀取動作決定的,而不管其他什么動作已經讀取同一位置。 在我們的示例中,這意味着即使“讀取1”可以讀取非null實例,但代碼隨后繼續返回它,然后又讀取了一個原始值,並且可以讀取將返回的null實例!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.