2015-12-12 49 views
2

我使用類似同步緩存的項目

Cache<Integer, Item> cache; 

其中Item s爲相互獨立的,看起來像

private static class Item { 
    private final int id; 
    ... some mutable data 

    synchronized doSomething() {...} 
    synchronized doSomethingElse() {...} 
} 

這樣做是爲了獲得從緩存中的項目,調用一個同步的方法。如果遺漏,該項目可以重新創建,沒關係。

當某個項目從緩存中被逐出並在線程運行同步方法時重新創建時會出現問題。一個新線程獲得一個新項目並在其上進行同步......因此,對於單個id,同步方法內有兩個線程。失敗。

有沒有簡單的方法呢?它是Guava Cache,如果有幫助。

+1

我可能會嘗試使用一個單獨的條紋''獲取每個鍵鎖? –

+0

如果項目較少相比,在高速緩存中的項目總數,如果在項目對象中的可變數據進行更新的數量不是太大,那麼怎麼樣同步的項目對象的深克隆,然後原子地更新可變緩存中相應項目中的數據。 – akki

回答

1

我認爲這個建議從路易,使用該鍵鎖定是最簡單和實用的。下面是一些代碼片段,即,沒有番石榴庫的幫助下,描述了一個思路:

static locks[] = new Lock[ ... ]; 
static { /* initialize lock array */ } 
int id; 
void doSomething() { 
    final lock = locks[id % locks.length]; 
    lock.lock(); 
    try { 
    /* protected code */ 
    } finally { 
    lock.unlock(); 
    } 
} 

鎖數組的大小限制了你的最大並行量。如果您的代碼僅使用CPU,則可以通過可用處理器的數量對其進行初始化,這是完美的解決方案。如果代碼等待I/O,則可能需要任意大量的鎖,或者限制可運行臨界區的線程數。在這種情況下,另一種方法可能會更好。在更概念層面

評論:

如果你想防止被驅逐的項目,你需要一個叫做機制釘扎。在大多數緩存實現中,這在內部使用,例如,用於在I/O操作期間阻塞。有些緩存可能會暴露出應用程序執行此操作的方式。

在JCache兼容緩存中,有一個EntryProcessor的概念。 EntryProcessor允許您以原子方式處理條目上的代碼。這意味着緩存正在爲您完成所有鎖定。根據問題的範圍,這可能有一個優點,因爲這也適用於集羣方案,這意味着鎖定是集羣範圍。

我想到的另一個想法是可否驅逐。這是EHCache 3正在實施的一個概念。通過指定一項可否決的驅逐政策,您可以自行實施固定機制。

+0

番石榴的版本是使用權重爲零,因此沒有資格進入由於尺寸的限制。 Ehcache的否決權只是一個暗示,可能不會被尊重,所以它的合同有點令人驚訝。 –

+0

@BenManes的問題是,「權重被測量和記錄當條目被插入到高速緩存中,並且因此一個高速緩存條目的壽命期間有效地靜態的。」,而我需要的釘扎的鎖的持續時間。 – maaartinus

+1

@maaartinus對不起,我只是迴應否決權是如何工作的番石榴,而不是做一個建議,幫助您解決問題。您的問題感覺就像用戶在將緩存用作對象池時遇到的問題(在讀取時獨佔訪問資源)。您可能希望查找「鍵控對象池」庫。 –

1

我相信你的問題有多種解決方案。 我寫下了他們中的一個,使用每個ietmId獨特的鎖:

public class LockManager { 

private Map<Integer, Lock> lockMap = new ConcurrentHashMap<>(); 

public synchronized Lock getOrCreateLockForId(Integer itemId) { 
    Lock lock; 
    if (lockMap.containsKey(itemId)) { 
     System.out.println("Get lock"); 
     lock = lockMap.get(itemId); 
    } else { 
     System.out.println("Create lock"); 
     lock = new ReentrantLock(); 
     lockMap.put(itemId, lock); 
    } 
    return lock; 
} 

public synchronized Lock getLockForId(Integer itemId) { 
    Lock lock; 
    if (lockMap.containsKey(itemId)) { 
     System.out.println("get lock"); 
     return lockMap.get(itemId); 
    } else { 
     throw new IllegalStateException("First lock, than unlock"); 
    } 
} 

}

因此,而不是使用類項目同步方法使用鎖管理器通過的itemId獲得鎖定和調用lock .lock()被檢索後。 另請注意,LockManager應該具有單例作用域,並且應該在所有用法中共享相同的實例。

下面你可以看到使用例如鎖管理的:

  try { 
      lockManager.getOrCreateLockForId(itemId).lock(); 
      System.out.println("start doing something" + num); 
      try { 
       Thread.sleep(5000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      System.out.println("completed doing something" + num); 
     } finally { 
      lockManager.getLockForId(itemId).unlock(); 
     } 
+0

有積累無限數量的鎖的問題。在很多情況下可能可以接受...... – maaartinus

+0

解鎖之後,從地圖上移除鎖定是有意義的。我只是想告訴你一個辦法。它可以根據需要在實際應用中要添加一些變化 – evgeniy44