Spring @Cacheable 双重检查锁定(infinispan 后端)

Spring @Cacheable double-checked locking (infinispan backend)

我最近开始在应用程序中使用 Spring 的 @Cacheable 注释来缓存一些昂贵操作的结果。我要替换的代码广泛使用了双重检查锁定模式,以确保缓存的一致性和仅执行一次昂贵的例程,如下所示:

final String key = SomeClass.computeKey(input)
String cachedValue = mCache.get(key);
if (cachedValue == null) {
    synchronized (mCache) {
        cachedValue = mCache.get(key);
        if (cachedValue == null) {
            cachedValue = expensiveComputing(input);  // must not be executed twice
            mCache.put(key, cachedValue);
        }
    }
}

以上被替换为:

@Cacheable(value="someCache", key="T(SomeClass).computeKey(#input)")
public String expensiveComputing(Object input) {
    // must not be executed twice
} 

有时会有第二个缓存来缓存实际查找的锁定对象,以避免在计算缓存值期间对整个缓存进行大量同步。

我已经阅读 infinispan's docu on locking 并且仍然想知道我是否需要自己处理这些方面,或者 infinispan 是否保证预期的行为。

根据文档,自 5.0 以来,infinispan 默认为每个缓存条目创建一个新锁(我使用的是 8.2.6)。那就是锁定对象和缓存值的 2 级缓存应该是不必要的,如果我理解正确的话?!但是双重检查锁定模式呢?这个也能去掉吗? expensiveComputing 例程在高度并发的环境中是否仍然只被调用一次?

简答

Spring 4.3+ 和 Infinispan 9.0+ 支持 @Cacheable 中的 sync=true(参见 this Spring ticket and this Infinispan ticket

长答案

Pre-Spring 4.3,简单地用@Cacheable注释方法不会同步代码本身。它所做的只是将方法包装到代理中(有关详细信息,请参阅 o.s.c.a.JCacheCacheAspecto.s.c.j.i.JCacheAspectSupport),当它在调用方法主体之前尝试从缓存中获取值时,不包括任何额外的同步。

因此,昂贵的计算完全有可能发生两次。在这种情况下,额外的同步取决于程序员。所以双重检查锁定还是有用的。

还有另一种选择:由于 Infinispan 将其缓存库作为 JCache (JSR-107) 提供程序提供,您可以考虑编写自己的 [=16= 实现,而不是使用默认 org.infinispan.jcache.annotation.InjectedCacheResultInterceptor ],这将负责锁定。例如,您的 class 实现可能有一些字典来决定哪种 InvocationContext 需要这种锁定。

实际上,在这种情况下使用 @Cacheable 似乎是痛苦多于收获。从事务管理器创建缓存并使用它来控制事务,就像他们在 Infinispan 中所做的那样 tutorials 似乎更容易和更简洁。

从 Spring 4.3 开始,@Cacheable 有一个 sync 属性,暗示整个操作必须同步。 Infinispan 从 9.0 版本开始支持这个。