2013-04-01 72 views
0

我需要確保多個線程不會同時嘗試訪問同一個資源。我有一堆這些資源,所以我想爲每個資源(而不是一個全局鎖)分別設置一個鎖對象,以便線程不會不必要地阻塞彼此。在一個id上同步並刪除

埃迪介紹了這https://stackoverflow.com/a/659939/82156使用ConcurrentMap.putIfAbsent()一個很好的解決方案。

// From https://stackoverflow.com/a/659939/82156 
public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>(); 

private Object getCacheSyncObject(final Integer id) { 
    locks.putIfAbsent(id, id); 
    return locks.get(id); 
} 

但是,該實現的一個問題是散列映射將無限增長。有沒有辦法在線程完成時刪除散列鍵而不會影響併發性?

+0

和'locks.remove(id,id)'有什麼問題? – OldCurmudgeon

+0

鎖定圖將比實際緩存小得多。是否有機制清理緩存?如果是這樣,它應該也可以清理鎖定圖。 – flup

回答

1

Pyrce讓我意識到並不是每個資源都有自己的鎖對象。相反,我可以使用可以在資源之間共享的100個鎖對象池。這樣,鎖對象的集合不會無限增長,但我仍然可以獲得我希望通過移除單個全局鎖獲得的大部分併發優勢。

這意味着我不再需要使用ConcurrentHashMap,並可以改爲只使用一個簡單的數組,急切地初始化。

// getPage() remains unchanged 
public Page getPage(Integer id) { 
    Page p = cache.get(id); 
    if (p == null) { 
    synchronized (getCacheSyncObject(id)) { 
     p = getFromDataBase(id); 
     cache.store(p); 
    } 
    } 
} 

private int MAX_LOCKS = 100; 
private Object[] locks = new Object[MAX_LOCKS]; 

{ 
    for(int i=0; i<locks.length; ++i) 
     locks[i] = new Object(); 
} 


private Object getCacheSyncObject(final Integer id) { 
    return locks[ id % MAX_LOCKS ]; 
} 
1

如果你知道某個特定線程是最後一個線程請求特定的鎖,那麼你可以只使用locks.remove(id, id)。否則,您需要對兩個無法輕鬆同步的事件進行原子更新。

如果您釋放並移除鎖定,另一個線程可能會抓取您釋放的鎖定,而另一個線程會使新的鎖定對象無視原始鎖定對象。在這種情況下,您最終可能會同時使用兩個線程調用getFromDataBase(id)cache.store(p)。如果你刪除然後釋放,你可能有另一個線程等待你釋放舊鎖,而新線程已經創建了新的鎖。同樣的碰撞可能發生。

基本上你不能原子釋放鎖,並從HashMap中不增加新鎖的系統中刪除。在這種情況下,您最終會得到一個全局鎖 - 這會破壞哈希特定鎖定的加速 - 或者您需要爲每個頁面添加一個額外的鎖,該鎖本身將具有相同的刪除問題。或者,如果你有權訪問HashMap的內部組件,你可以嘗試一些棘手的桶鎖暴露給你的更高級別的邏輯,但我不建議嘗試實現這樣的事情。

一個解決方案是限制的散列映射的大小是使用固定大小的映射來代替。使用大數(10,000)修改您的Integer id,並使用修飾的數字作爲唯一的鎖ID。兩個哈希碰撞並且要求不同頁面使用相同的鎖定的概率非常小,並且您對鎖定消耗的內存有一定的限制。在這種情況下,您實際上不需要HashMap,因爲您可以將Integer鎖對象預先分配到靜態常量數組中,並直接從id對象請求mod哈希。

而且你應該小心做緩存的鎖之外,除非讀取緩存本身自帶的保護讀取寫入作爲一個線程可以寫,而另一個從代碼在一個問題中指定的方式讀取。

+0

對於建議將id修改爲固定大小的整數列表的+1。您可能偶爾會將兩個不同的資源調整到同一個鎖上,但對我的目的來說沒關係。 – emmby