这是我的加载缓存定义:
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的值,直到下一次更新发生。我完全混淆了逻辑I&ve; ve的并发含义:
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
之间,这个引用可以被其他一些线程覆盖。因为我已经覆盖了&#39;价值&#39;使用已存在于缓存中的引用,我是否需要将键值对放入缓存中?我需要第2
行吗?
答案 0 :(得分:2)
(免责声明:我不是番石榴缓存专家)
我认为您的代码中存在两个并发问题:
existingValues.regionAValue = ...
和existingValues.setRegionValue(...)
。当只应用一个操作时,其他线程可以看到状态。我认为这不是通缉。 (正确的吗?)get()
和put()
之间,该值可能会再次加载到缓存中,put()
会覆盖新值。关于1:
如果对对象有更多读取然后写入,一个好的选择是使用不可变对象。您不要触摸实例,而是执行原始对象的副本,变异,并将新对象放入缓存中。这样只有最终状态变得可见。
关于2:
Atomic CAS操作可以帮助您(例如JSR107兼容的缓存)。有用的方法是boolean replace(K key, V oldValue, V newValue);
在Google Guava中,可以通过ConcurrentMap接口访问CAS方法,您可以通过asMap()检索它。