如何以高效的方式同时防止多次加载缓存中不存在的值?
典型的缓存使用情况是以下伪代码:
Object get(Object key) {
Object value = cache.get(key);
if (value == null) {
value = loadFromService(key);
cache.set(key,value);
}
return value;
}
问题:在从服务(数据库,WebService,RemoteEJB或其他任何东西)加载值之前,可能会在同一时间进行第二次调用,这将使值再次加载。
例如,当我为用户X缓存所有项目,并且经常查看此用户并且有很多项目时,很可能同时调用所有项目的负载,导致服务器负载过重。
我可以使get
功能同步,但这会迫使其他搜索等待,这没什么意义。我可以为每个密钥创建 new lock ,但我不知道在 Java 中管理如此大量的锁是否是个好主意(这部分是特定于语言的,我将其标记为java
)的原因。
或者我可以使用另一种方法?如果是这样,最有效的是什么?
答案 0 :(得分:7)
你可以做的一般事情就是使用Object的hashCode。
您可以拥有一个基于hashCode使用的锁数组,以减少冲突的可能性。或者作为一个黑客,你可以使用自动装箱字节总是返回相同对象的事实。
Object get(Object key) {
Object value = cache.get(key);
if (value == null) {
// every possible Byte is cached by the JLS.
Byte b = Byte.valueOf((byte) key.hashCode());
synchronized (b) {
value = cache.get(key);
if (value == null) {
value = loadFromService(key);
cache.set(key, value);
}
}
}
return value;
}
答案 1 :(得分:3)
不要重新发明轮子,使用番石榴LoadingCache
或memoizing supplier。
如果您使用的是Ehcache,请阅读read-through,这是您要求的模式。您必须实现CacheEntryFactory
接口以指示缓存如何读取缓存未命中的对象,并且必须使用Ehcache
的实例包装SelfPopulatingCache
实例。
答案 2 :(得分:1)
对于加载时,在地图中插入一个中间对象而不是结果,以指示加载已开始但尚未完成。 java.util.concurrent.FutureTask下面用于中间对象:
Object get(final Object key) throws Exception {
boolean doRun = false;
Object value;
synchronized (cache) {
value = cache.get(key);
if (value == null) {
value = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
Object loadedValue = loadFromService(key);
synchronized (cache) {cache.put(key, loadedValue);};
return loadedValue;
}
});
cache.put(key, value);
doRun=true;
}
}
if (value instanceof FutureTask) {
FutureTask task = (FutureTask) value;
if (doRun) {
task.run();
}
return task.get();
}
return value;
}`