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

Java 如何防止多次同时加载非缓存值?,java,performance,caching,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或其他任何东西)加载值之前,可能会同时进行第二

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

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

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
功能同步化,但这会迫使其他搜索等待,没有多大意义。我可以为每个密钥创建新锁,但我不知道在Java中管理如此大量的锁是否是个好主意(这部分是特定于语言的,我将其标记为
Java


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

通常可以使用对象的哈希代码

您可以有一个基于哈希代码使用的锁数组,以减少冲突的机会。或者作为一个黑客,你可以利用自动装箱字节总是返回相同的对象这一事实

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;
}

不要重新发明轮子,用番石榴或其他蔬菜


如果您正在使用Ehcache,请阅读,这就是您所要求的模式。必须实现
CacheEntryFactory
接口,以指示缓存如何读取缓存未命中的对象,并且必须在加载时将
Ehcache
实例包装为
SelfPopulatingCache

,在映射中插入中间对象而不是结果,以指示加载已开始但尚未完成。以下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;
}`

你想得太多了,说真的。除非从服务加载数据的时间太长,否则这永远不会是问题。我有一些外来的EJB代码,在测试环境中可能需要20秒,所以我担心10或20个并发请求会发生什么基于哈希代码的锁池的好主意!但在获得锁后,您可以找到其他进程缓存的值,所以您还应该检查它是否已加载:)哇,我从未想过以这种方式使用字节!更重要的是,这是我第一次看到valueOf:)的字节值池的实际用法。你能解释一下“每个可能的字节都由JLS缓存”是什么意思吗?是像JVM从一开始就缓存它们,还是因为它们的不可变性,它们最终会被缓存在内存中?好吧,也许这就是答案:据我所知,CacheLoader正在做我所期望的事情,对所需的同步进行内部管理?是的,它提供了更多的功能-逐出、删除侦听器等等。我明白了,有趣的是,我使用的是EcHACHE,但我可以考虑使用番石榴,但是EHCACH支持溢出到磁盘,而且,如何实现它本身是有趣的。如果您正在使用EHCHACE,请阅读,这是您要的模式。我认为Ehcache也支持这一点。嗯,您的解决方案要求始终对整个缓存进行同步,但同步部分相当快。您认为如何在初始获取时执行,并仅在值为null时运行synchronized part?这取决于您期望每秒对缓存的请求数。同步部分的长度小于1微秒,因此,如果您的速率小于每秒100000个请求,则冲突的可能性可以忽略不计,因此任何复杂情况都不会产生任何影响。如果速率更高,那么还有另一种情况,您必须考虑许多不同的因素,包括处理器缓存、线程切换和垃圾收集器,从性能角度来看,对缓存的访问可能不是第一位的。