2010-04-14 47 views
1

我有緩存用戶信息而不是在每個請求(共享Ehcache)上從用戶存儲中檢索它的servlet。我的問題是,如果客戶端是多線程和他們做一個以上的併發請求,他們已經通過驗證前,然後我得到這個在我的日誌:鎖定緩存密鑰而不鎖定整個緩存

Retrieving User [Bob] 
Retrieving User [Bob] 
Retrieving User [Bob] 
Returned [Bob] ...caching 
Returned [Bob] ...caching 
Returned [Bob] ...caching 

我想的是,第一個請求就撥打的用戶服務,而其他兩個請求被阻塞 - 與第一個請求返回時,然後緩存對象,另外兩個請求都通過:

Retrieving User [Bob] 
blocking... 
blocking... 
Returned [Bob] ...caching 
[Bob] found in cache 
[Bob] found in cache 

我想過上鎖定字符串「鮑勃」(因爲實習它始終是同一個對象?)。這會起作用嗎?如果是這樣的話,我該如何跟蹤實際存在於緩存中的密鑰並在它們周圍建立一個鎖定機制,然後在檢索到該對象後返回該有效對象。謝謝。

回答

4

如果您沒有鎖的獨佔控制權,則不能保證不會發生死鎖。實習String全球可見,所以他們是一個可憐的候選人。

而是使用鍵與其對應的鎖之間的映射。您可以使用對併發映射的同步訪問,或使用ConcurrentMap。我不確定哪個更便宜,但我傾向於ConcurrentMap,因爲它表達了你的意圖。

ReadWriteLock trial = new ReentrantReadWriteLock(fair); 
ReadWriteLock lock = locks.putIfAbsent(key, trial); 
if (lock == null) { 
    /* The current thread won the race to create lock for key. */ 
    lock = trial; 
} 

(使用ReadWriteLock是可選的,有了它,你可以做一些花哨的像允許由多個線程緩存值的併發讀取,但仍然有另一個線程獲得一個獨佔鎖,當值需要更新)

創建大量的鎖因爲鎖已經存在而最終被丟棄可能會很昂貴。或者,您可以使用不帶java.util.concurrent的舊運行時。在這樣的情況下,你可以在地圖上進行同步:

Object lock; 
synchronized (locks) { 
    if (locks.containsKey(key)) 
    lock = locks.get(key); 
    else { 
    lock = new Object(); 
    locks.put(key, object); 
    } 
} 
+0

這非常接近我的想法,因爲某些原因,鎖定在實際上的字符串看起來像是一個非常糟糕的主意。但在這種情況下,我鎖定了Map上的每個調用 - 但我想這不是那麼不同(性能明智),然後我的緩存由所有線程共享。我認爲即使這種開銷仍然快得多,然後對我的用戶身份驗證提供程序進行多次不必要的調用。 – Gandalf 2010-04-14 19:53:39

0

如果您使用Map進行緩存,那麼鎖定密鑰就會按照您的建議進行操作。