[英]Effectively Immutable Object
我想確保根據Java內存模型正確理解'有效不可變對象'的行為。
假設我們有一個可變類,我們希望將其發布為有效的不可變類:
class Outworld {
// This MAY be accessed by multiple threads
public static volatile MutableLong published;
}
// This class is mutable
class MutableLong {
private long value;
public MutableLong(long value) {
this.value = value;
}
public void increment() {
value++;
}
public long get() {
return value;
}
}
我們執行以下操作:
// Create a mutable object and modify it
MutableLong val = new MutableLong(1);
val.increment();
val.increment();
// No more modifications
// UPDATED: Let's say for this example we are completely sure
// that no one will ever call increment() since now
// Publish it safely and consider Effectively Immutable
Outworld.published = val;
問題是 :Java內存模型是否保證所有線程都必須具有Outworld.published.get() == 3
?
根據Java Concurrency In Practice,這應該是真的,但如果我錯了,請糾正我。
3.5.3。 安全出版習語
要安全地發布對象,必須同時使對象的引用和對象的狀態對其他線程可見。 正確構造的對象可以通過以下方式安全發布:
- 從靜態初始化程序初始化對象引用;
- 將對它的引用存儲到易失性字段或AtomicReference中;
- 將對它的引用存儲到正確構造的對象的最終字段中; 要么
- 將對它的引用存儲到由鎖正確保護的字段中。3.5.4。 有效不可變的對象
安全發布的有效不可變對象可以被任何線程安全地使用而無需額外的同步。
是。 MutableLong
上的寫操作之后是讀之前的happens-before
關系(在volatile上)。
(有可能一個線程讀取Outworld.published
並將其不安全地傳遞給另一個線程。理論上,這可以看到早期狀態。在實踐中,我看不到它發生。)
Java內存模型必須滿足幾個條件才能保證Outworld.published.get() == 3
:
MutableLong
,然后設置Outworld.published
字段, 必須在步驟之間顯示可見性 。 實現這一目標的一種方法是讓所有代碼在單個線程中運行 - 保證“ as-if-serial語義 ”。 我認為這是你的意圖,但認為值得指出。 Outworld.published
必須發生 - 在賦值語義之后 。 一個例子就是讓同一個線程執行Outworld.published = val;
然后啟動其他可以讀取值的線程。 這將保證“ 就像串行 ”語義一樣,防止在賦值之前重新排序讀取。 如果您能夠提供這些保證,那么JMM將保證所有線程都能看到Outworld.published.get() == 3
。
但是,如果您對該領域的一般項目設計建議感興趣,請繼續閱讀。
對於沒有其他線程曾經看到一個不同的值保證Outworld.published.get()
你(開發商)必須保證你的程序不以任何方式修改的值。 隨后執行Outworld.published = differentVal;
或者Outworld.published.increment();
。 雖然可以保證,但如果您設計代碼以避免可變對象,並使用靜態非最終字段作為多個線程的全局訪問點,則可以更容易:
MutableLong
,將相關值復制到不同類的新實例,其狀態不能被修改。 例如:引入類ImmutableLong
,它在構造時為final
字段value
,並且沒有increment()
方法。 Callable
/ Runnable
實現。 這將防止一個流氓線程重新分配值並干擾其他線程的可能性,並且比靜態字段重新分配更容易推理。 (誠然,如果你正在處理遺留代碼,說起來容易做起來難)。 問題是:Java內存模型是否保證所有線程都必須具有Outworld.published.get()== 3?
簡短的回答是no
。 因為其他線程可能會在讀取之前訪問Outworld.published
。
在Outworld.published = val;
之后的那一刻Outworld.published = val;
已經完成,條件是沒有其他修改與val
- 是 - 它總是3
。
但是如果任何線程執行val.increment
那么其值對於其他線程可能會有所不同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.