锁定缓存密钥而不锁定整个缓存

时间:2010-04-14 18:59:08

标签: java performance caching concurrency

我有servlet缓存用户信息,而不是在每个请求(共享Ehcache)上从用户存储中检索它。我遇到的问题是,如果客户端是多线程的,并且在进行身份验证之前,他们会同时发出多个请求,那么我会在我的日志中得到这个:

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

我考虑过锁定字符串“Bob”(因为实际上它始终是同一个对象吗?)。那会有用吗?如果是这样的话,我如何跟踪缓存中实际存在的密钥并围绕它们构建一个锁定机制,然后一旦检索到它就会返回有效对象。感谢。

3 个答案:

答案 0 :(得分:4)

如果您没有对锁的独占控制,则无法保证不会出现死锁。 Interned 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);
  }
}

答案 1 :(得分:2)

  

我考虑过锁定字符串“Bob”(因为实际上它始终是同一个对象吗?)。那会有用吗?

I've previously tried this and it actually doesn't work quite like you might expect。您需要确保首先在字符串上调用intern();然而,在实习生字符串上同步实际上是一个非常糟糕的主意。

答案 2 :(得分:0)

如果你使用Map进行缓存,那么锁定密钥将按照你的建议进行。