[英]Is this lazy initialization pattern for objects in a hashmap thread-safe?
我想避免在可能的情況下鎖定讀取。 但這就像雙重檢查鎖定一樣,即使沒有涉及部分初始化的成員也是如此。
這是一個好結構嗎?
private final Map<String, Stuff> stash = new HashMap<String, Stuff>();
public Stuff getStuff(String name) {
if (stash.containsKey(name))
return stash.get(name);
synchronized(stash) {
if (stash.containsKey(name)) {
return stash.get(name);
}
else {
Stuff stuff = StuffFactory.create(name);
stash.put(name, stuff);
return stuff;
}
}
}
不,此構造不是線程安全的。
假設線程writer
正在將某些東西放入映射中,並且映射太小,必須調整其大小。 這是在synchronized
塊內完成的,因此您可能認為還可以。
在調整大小期間,地圖中的任何內容均無法得到保證。
現在,同時,假設線程reader
為現有元素調用getStuff
。 該線程可以直接訪問地圖,因為在第一次調用containsKey
和get
,它沒有到達synchronized
塊。 它將查找處於未定義狀態的地圖,盡管它僅讀取,但它訪問內容未定義的數據。 可能的結果包括:
getStuff
不應該返回null
。 getStuff
返回預期的Stuff
。 getStuff
返回在調整大小時由HashMap
實現使用的一些內部對象。 getStuff
返回與該名稱無關的其他Stuff
。 getStuff
陷入無限循環。 這只是顯而易見的情況,應該易於理解。 所以不,當存在設計良好的類(例如ConcurrentHashMap
或Guava的MapMaker
時,請不要使用快捷方式。
順便說一句:先調用containsKey
,然后再get
相同的鍵,效率很低。 只需調用get
,保存結果並將其與null
進行比較即可。 您將在地圖中保存一個搜索操作。
用ConcurrentHashMap
替換HashMap
,就可以了。
更一般的解決方案必須擔心以下問題
1。 基於名稱的鎖定,而不是全局鎖定。 如果create(n1)
塊,則不應影響對其他名稱的操作。
2。 如果create()
返回null或引發異常該怎么辦。 有趣的是,這對某些暗示是個問題。
3。 如果create(n1)
調用get(n1)
怎么辦? 我們將進行遞歸。 一些展示掛起。 一些impl檢測到遞歸並引發錯誤。 一個更好的impl應該允許遞歸運行(終止或堆棧溢出),並且中間結果對於鎖定線程應該是可見的,而對於其他線程則是不可見的,直到遞歸終止為止。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.