这是我的加载缓存定义:
private class ProductValue {
private long regionAValue;
private long regionBValue;
// constructor and general stuff here
}
private final LoadingCache<ProductId, ProductValue> productCache = CacheBuilder.newBuilder()
.expireAfterAccess(4, TimeUnit.MINUTES)
.build(new CacheLoader<ProductId, ProductValue>() {
@Override
public ProductValue load(final ProductId productId) throws Exception {
return updateProductValues(productId);
}
});
private ProductValue updateProductValues(final ProductId productId) {
// Read from disk and return
}
现在,我有一个用例,需要在高速缓存中设置regionA或regionB的值,直到下一次更新发生为止。我对自己所具有的逻辑的并发含义感到困惑:
public void setProductValue(final ProductId productId, final boolean isTypeA, final long newValue) throws ExecutionException {
ProductValue existingValues = productCache.get(productId); // 1
if (isTypeA) {
existingValues.regionAValue = newValue;
} else {
existingValues.regionBValue = newValue;
}
productCache.put(productId, existingValues); // 2
}
在
1
中,我只是读取给定密钥存储在缓存中的信息的引用,此get是线程安全的,因为加载缓存的行为类似于并发映射。但是在1
和2
之间,此引用可以被其他一些线程覆盖。由于我已经使用缓存中已经存在的引用覆盖了“值”,因此是否需要将键值对放入缓存中?我需要行2
吗? 最佳答案
(免责声明:我不是Guava Cache专家)
我认为您的代码中存在两个并发问题:
您可以通过两个操作来更改现存值中的对象,即existingValues.regionAValue = ...
和existingValues.setRegionValue(...)
。当仅应用一个操作时,其他线程可以看到状态。我认为这是不想要的。 (正确?)
在get()
和put()
之间,该值可能会再次加载到缓存中,并且put()
会覆盖一个新值。
关于1:
如果对对象的读取多于写入,那么不错的选择是使用不可变的对象。您无需触摸实例,但可以复制原始对象,进行变异,然后将新对象放入缓存中。这样,只有最终状态才可见。
关于2:
原子CAS操作可以为您提供帮助(例如,与JSR107兼容的缓存)。有用的方法是boolean replace(K key, V oldValue, V newValue);
在Google Guava中,可以通过ConcurrentMap接口访问CAS方法,您可以通过asMap()进行检索。
关于java - 将值放入Google Guava loadingCache,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26622721/