这是我的加载缓存定义:

  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是线程安全的,因为加载缓存的行为类似于并发映射。但是在12之间,此引用可以被其他一些线程覆盖。由于我已经使用缓存中已经存在的引用覆盖了“值”,因此是否需要将键值对放入缓存中?我需要行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/

10-12 05:48