我有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”(因为实际上它始终是同一个对象吗?)。那会有用吗?如果是这样的话,我如何跟踪缓存中实际存在的密钥并围绕它们构建一个锁定机制,然后一旦检索到它就会返回有效对象。感谢。
答案 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进行缓存,那么锁定密钥将按照你的建议进行。