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.JCacheCacheAspect
和 o.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 版本开始支持这个。
我最近开始在应用程序中使用 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.JCacheCacheAspect
和 o.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 版本开始支持这个。