[英]Resetting a field lazy-loaded with the double-check idiom
考慮“對實例字段的延遲初始化進行雙重檢查”:
//在Bloch的采訪中復制了Effective Java中的第71項。\n private volatile FieldType字段;\n FieldType getField(){\n FieldType結果=字段;\n if(result == null){//首先檢查(無鎖定)\n synchronized(this){\n result = field;\n if(result == null)//第二次檢查(帶鎖定)\n field = result = computeFieldValue();\n }\n }\n 返回結果;\n }
我希望能夠以安全的方式重置字段(強制它再次從數據庫加載,在我的情況下)。 我假設我們可以通過重置方法來做到這一點:
\n void reset(){\n field = null;\n }
這是重置場地的標准方法嗎? 安全嗎? 任何陷阱? 我問,因為布洛赫發出了關於雙重檢查懶惰加載的以下警告:“成語非常快,但也很復雜和細膩,所以不要試圖以任何方式修改它。只需復制和粘貼 - 通常這不是一個好主意,但在這里是合適的。“
在此先感謝喜馬拉雅山脈的Playa。
是的,這是線程安全的。
synchronized塊用於防止多個線程不必要地調用computeFieldValue()
。 由於field
是易失性的,因此reset
和getField
中的訪問都是有序的。
如果第一次檢查是非null,則getField
完成; 返回result
。
否則,獲取鎖,排除可能將該字段設置為非null的任何其他線程,但允許任何線程將field
設置為null。 如果任何線程將field
設置為null,則不應該更改任何內容; 這是使線程進入同步塊的條件。 如果另一個線程在當前線程檢查后已經獲取了鎖,並將該字段設置為非空值,則第二個檢查將檢測到該值。
我認為這應該是安全的,但這只是因為你將字段存儲在局部變量中。 完成此操作后,即使另一個線程正在重置字段的值,也無法將局部變量引用神奇地更改為null。
我認為reset()方法不正確。 如果你閱讀第71項,你會發現:
此代碼可能看起來有點復雜。 特別是,對局部變量結果的需求可能不清楚。 這個變量的作用是確保該字段在已經初始化的常見情況下只讀一次。
延遲初始化不會假設該字段可能已更改。 如果這些運算符之間的字段將設置為null:
FieldType result = field;
if (result == null) { // First check (no locking)
getField()提供不正確的結果。
只要reset方法是上面列出的reset()
方法,這似乎就可以工作。 但是,如果reset()
方法實例化一個新的Object(如下所示),你最終是否可能返回與你想要的不同的東西?
void reset() {
field = new FieldType();
}
我想這完全取決於線程安全的確切含義。
最終可能會遇到第二個實例后使用第一個實例的情況。 這可能沒問題,也可能沒有。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.