2011-03-08 48 views
0

我煞費苦心地提出問題。我有以下片(Java)的代碼(僞)的:保持「顯而易見」的鎖定檢索或使用雙重檢查鎖定?

public SomeObject getObject(Identifier someIdentifier) { 
    // getUniqueIdentifier retrieves a singleton instance of the identifier object, 
    // to prevent two Identifiers that are equals() but not == (reference equals) in the system. 
    Identifier singletonInstance = getUniqueIdentifier(someIdentifier); 
    synchronized (singletonInstance) { 
     SomeObject cached = cache.get(singletonInstance); 
     if (cached != null) { 
      return cached; 
     } else { 
      SomeObject newInstance = createSomeObject(singletonInstance); 
      cache.put(singletonInstance, newInstance); 
      return newInstance; 
     } 
    } 
} 

基本上,它使一個識別符「獨特的」(參考值等於,如在==),檢查高速緩衝存儲器,並且在一個高速緩存未命中的情況下,調用一個昂貴的方法(涉及調用外部資源和解析等),將其放入緩存中並返回。在這種情況下,同步的Identifier避免了兩個equals()而不是==Identifier對象被用來調用昂貴的方法,它將同時檢索相同的資源。

以上作品。我只是想知道,並且可能是微型優化,會改寫如下,它會採用更簡單的緩存檢索,並且雙重檢查鎖定是'安全'的(在線程安全中是安全的,沒有奇怪的競爭條件)並且'更多最優「(如減少不必要的鎖定和線程必須等待鎖定)?

public SomeObject getObject(Identifier someIdentifier) { 

    // just check the cache, reference equality is not relevant just yet. 
    SomeObject cached = cache.get(someIdentifier); 
    if (cached != null) { 
     return cached; 
    }   

    Identifier singletonInstance = getUniqueIdentifier(someIdentifier); 
    synchronized (singletonInstance) { 
     // re-check the cache here, in case of a context switch in between the 
     // cache check and the opening of the synchronized block. 
     SomeObject cached = cache.get(singletonInstance); 
     if (cached != null) { 
      return cached; 
     } else { 
      SomeObject newInstance = createSomeObject(singletonInstance); 
      cache.put(singletonInstance, newInstance); 
      return newInstance; 
     } 
    } 
} 

你可以說「只是測試它」或「只是做一個微標杆」,但測試的代碼多線程位是不是我的強項,我懷疑我能夠模擬現實的情況或準確的假競賽條件。再加上它會帶我半天,而寫一個SO問題只需要幾分鐘:)。

+2

預警:[雙重檢查鎖定在Java中被破壞。](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) – cHao 2011-03-08 14:44:19

+0

cHao該論文描述了DCL之前的狀態Java5內存模型。在新的內存模型下,volatile的語義是固定的,以便DCL模式能夠正常工作。但是,使用封裝惰性的util始終是首選:https://labs.atlassian.com/wiki/display/CONCURRENT/LazyReference+and+ResettableLazyReference – 2011-03-08 22:13:31

回答

0

您重塑谷歌的集合/番石榴的地圖製作工具/ ComputingMap:

ConcurrentMap<Identifier, SomeObject> cache = new MapMaker().makeComputingMap(new Function<Identifier, SomeObject>() { 
    public SomeObject apply(Identifier from) { 
    return createSomeObject(from); 
    } 
}; 

public SomeObject getObject(Identifier someIdentifier) { 
    return cache.get(someIdentifier); 
} 

實習在這裏沒有必要爲ComputingMap保證單個線程只嘗試填充,如果沒有,而另一個線程要求同一個項目會阻止並等待結果。如果您刪除正在填充的密鑰,那麼該線程和當前正在等待的任何密鑰仍然會得到該結果,但隨後的請求將再次啓動該羣體。

如果您確實需要實習,那麼該庫提供了具有強引用和弱引用緩存的出色Interner類。

0

同步需要2微秒。除非你需要進一步削減,否則用最簡單的解決方案可能會更好。

BTW你可以寫

SomeObject cached = cache.get(singletonInstance); 
if (cached == null) 
    cache.put(singletonInstance, cached = createSomeObject(singletonInstance)); 
return cached; 
0

如果「緩存」是一個地圖(我懷疑它是),那麼這個問題不是一個簡單的雙重檢查鎖定的問題完全不同。

如果cache是​​一個普通的HashMap,那麼問題其實更糟糕;即您提出的「雙重檢查模式」比簡單的基於參考的雙重檢查更糟糕。事實上,它可能導致ConcurrentModificationExceptions,得到不正確的值,甚至導致無限循環。

如果它基於一個普通的HashMap,我會建議使用ConcurrentHashMap作爲第一種方法。使用ConcurrentHashMap,您不需要明確的鎖定。

public SomeObject getObject(Identifier someIdentifier) { 
    // cache is a ConcurrentHashMap 

    // just check the cache, reference equality is not relevant just yet. 
    SomeObject cached = cache.get(someIdentifier); 
    if (cached != null) { 
     return cached; 
    }   

    Identifier singletonInstance = getUniqueIdentifier(someIdentifier); 
    SomeObject newInstance = createSomeObject(singletonInstance); 
    SombObject old = cache.putIfAbsent(singletonInstance, newInstance); 
    if (old != null) { 
     newInstance = old; 
    } 
    return newInstance; 
} 
+0

原始問題具體詢問避免重複創建對象。這個答案會從多個線程調用'createSomeObject(singletonInstance)'。基本上Guava MapMaker.makeComputingMap()的語義是這裏所要求的。 – 2011-03-10 23:05:19