考虑到旧密钥的 Guava 缓存
Guava cache considering older key
我遇到了 Guava Caches 的问题。当我在缓存中只有一个元素时,一切都很好。但是当我加载第二个元素时,它会尝试使用较早条目的键进行选择
private static LoadingCache<String, MyClass> cache = null;
....
public MyClass method(final String id1, final long id2) {
log.error("inside with "+id1);
final String cacheKey = id1+"-"+id2;
if(cache == null){
cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<String, MyClass>() {
@Override
public MyClass load(String key) {
return getValue(cacheKey);
}
}
);
}
try {
return cache.get(cacheKey);
} catch (ExecutionException ex) {
log.error("EEE missing entry",ex);
}
}
private MyClass getValue(String cacheKey){
log.error("not from cache "+cacheKey);
...
}
日志说:
inside with 129890038707408035563943963861595603358
not from cache 1663659699-315839912047403113610285801857400882820 // This is key for the earlier entry
例如,当我调用 method("1", 2) 时,它会将值加载到缓存中,随后我可以从缓存中获取它。现在我调用方法 ("3", 4),这不在缓存中,因此调用 getValue() 并且日志打印出方法 ("1", 2)
的键
我哪里错了?
您的问题与您如何创建 CacheLoader
有关缓存被延迟初始化的时间)而它应该更通用并且依赖于作为参数提供给 CacheLoader
的方法 load
的 key
否则它将通过调用 getValue(key)
使用相同的密钥。
应该是这样的:
new CacheLoader<String, MyClass>() {
@Override
public MyClass load(String key) {
return getValue(key); // instead of return getValue(cacheKey);
}
}
注意:你初始化缓存的方式不是线程安全的,事实上如果它还没有被初始化并且你的方法method
被多个线程同时调用它将被创建多次而不是一次。
一种方法是使用双重检查锁定习惯用法,如下所示:
private static volatile LoadingCache<String, MyClass> cache = null;
public MyClass method(final String id1, final long id2) {
...
if(cache == null){
synchronized (MyClass.class) {
if(cache == null){
cache = ...
}
}
}
注意:不要使用基于非静态的CacheLoader
初始化静态缓存 方法,它太容易出错了。将它们设为 非静态 或 静态 但不要混合使用。
假设你可以使两者都静态,你的缓存初始化将非常简单,它只是:
private static final LoadingCache<String, MyClass> cache = CacheBuilder.newBuilder()...
不需要懒惰地初始化它,这也会大大简化你的方法的代码,因为它会简单地减少到:
public MyClass method(final String id1, final long id2) {
log.error("inside with "+id1);
final String cacheKey = id1+"-"+id2;
try {
return cache.get(cacheKey);
} catch (ExecutionException ex) {
log.error("EEE missing entry",ex);
}
}
我遇到了 Guava Caches 的问题。当我在缓存中只有一个元素时,一切都很好。但是当我加载第二个元素时,它会尝试使用较早条目的键进行选择
private static LoadingCache<String, MyClass> cache = null;
....
public MyClass method(final String id1, final long id2) {
log.error("inside with "+id1);
final String cacheKey = id1+"-"+id2;
if(cache == null){
cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<String, MyClass>() {
@Override
public MyClass load(String key) {
return getValue(cacheKey);
}
}
);
}
try {
return cache.get(cacheKey);
} catch (ExecutionException ex) {
log.error("EEE missing entry",ex);
}
}
private MyClass getValue(String cacheKey){
log.error("not from cache "+cacheKey);
...
}
日志说:
inside with 129890038707408035563943963861595603358
not from cache 1663659699-315839912047403113610285801857400882820 // This is key for the earlier entry
例如,当我调用 method("1", 2) 时,它会将值加载到缓存中,随后我可以从缓存中获取它。现在我调用方法 ("3", 4),这不在缓存中,因此调用 getValue() 并且日志打印出方法 ("1", 2)
的键我哪里错了?
您的问题与您如何创建 CacheLoader
有关缓存被延迟初始化的时间)而它应该更通用并且依赖于作为参数提供给 CacheLoader
的方法 load
的 key
否则它将通过调用 getValue(key)
使用相同的密钥。
应该是这样的:
new CacheLoader<String, MyClass>() {
@Override
public MyClass load(String key) {
return getValue(key); // instead of return getValue(cacheKey);
}
}
注意:你初始化缓存的方式不是线程安全的,事实上如果它还没有被初始化并且你的方法method
被多个线程同时调用它将被创建多次而不是一次。
一种方法是使用双重检查锁定习惯用法,如下所示:
private static volatile LoadingCache<String, MyClass> cache = null;
public MyClass method(final String id1, final long id2) {
...
if(cache == null){
synchronized (MyClass.class) {
if(cache == null){
cache = ...
}
}
}
注意:不要使用基于非静态的CacheLoader
初始化静态缓存 方法,它太容易出错了。将它们设为 非静态 或 静态 但不要混合使用。
假设你可以使两者都静态,你的缓存初始化将非常简单,它只是:
private static final LoadingCache<String, MyClass> cache = CacheBuilder.newBuilder()...
不需要懒惰地初始化它,这也会大大简化你的方法的代码,因为它会简单地减少到:
public MyClass method(final String id1, final long id2) {
log.error("inside with "+id1);
final String cacheKey = id1+"-"+id2;
try {
return cache.get(cacheKey);
} catch (ExecutionException ex) {
log.error("EEE missing entry",ex);
}
}