簡體   English   中英

哈希映射中對象的這種惰性初始化模式是否是線程安全的?

[英]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 該線程可以直接訪問地圖,因為在第一次調用containsKeyget ,它沒有到達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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM