我最近开始在应用程序中使用 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)。也就是说锁对象和缓存值的二级缓存应该是不必要的,如果我理解正确的话?!但是双重检查锁定模式又如何呢?这个也可以去掉吗?在高度并发的环境中,exppressiveComputing
例程是否仍然只被调用一次?
最佳答案
简短回答
Spring 4.3+ 和 Infinispan 9.0+ 支持此功能,并在 @Cacheable
中使用 sync=true
(请参阅 this Spring ticket 和 this Infinispan ticket)
长答案
Spring 4.3之前,简单地用@Cacheable
注释方法不会同步代码本身。它所做的只是将方法包装到代理中(有关详细信息,请参阅 o.s.c.a.JCacheCacheAspect
和 o.s.c.j.i.JCacheAspectSupport
),当它尝试从中获取值时,不包含任何额外的同步在调用方法体之前进行缓存。
因此,昂贵的计算完全有可能发生两次。在这种情况下,额外的同步取决于程序员。所以双重检查锁定还是有用的。
还有另一个选择:由于 Infinispan 将其缓存库作为 JCache (JSR-107) 提供程序提供,您可以考虑使用默认的 org.infinispan.jcache.annotation.InjectedCacheResultInterceptor,而不是使用默认的 org.infinispan.jcache.annotation.InjectedCacheResultInterceptor,编写您自己的 org.infinispan.jcache.annotation.AbstractCacheResultInterceptor 实现,它将负责锁定。例如,您的类的实现可能有一些字典来决定什么类型的 InitationContext
需要此类锁定。
实际上,在这种情况下使用@Cacheable
看起来弊大于利。从事务管理器创建缓存并使用它对事务进行一些控制,就像在 Infinispan 中那样 tutorials似乎更容易、更简洁。
自 Spring 4.3 起,@Cacheable
具有 sync属性暗示整个操作必须同步。 Infinispan 从 9.0 版本开始支持此功能。
关于java - Spring @Cacheable 双重检查锁定(infinispan 后端),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44880927/