如何防止多次同时加载非缓存值?

时间:2013-01-23 10:06:47

标签: java performance caching

如何以高效的方式同时防止多次加载缓存中不存在的值?

典型的缓存使用情况是以下伪代码:

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)的原因。

或者我可以使用另一种方法?如果是这样,最有效的是什么?

3 个答案:

答案 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)

不要重新发明轮子,使用番石榴LoadingCachememoizing 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;
}`