如何从多个线程更新缓存



我有一个具有缓存(缓存类型)的 Runnable,我们假设它提供线程安全操作。此可运行对象由多个线程使用。

我们的线程从外部源获取对象,然后

  1. 检查缓存中是否存在对象
  2. 如果没有,则
  3. 如果它已经在缓存中,则更新

我正在寻找正确的方案(即最小的synchronized代码)来可靠地处理缓存。

我想出了以下方案:

MyObject current = cache.getIfPresent(givenKey);
if (current == null) {
MyObject prev = cache.asMap().putIfAbsent(givenKey, givenObj);
if (prev == null) {
// successful put in cache
return givenObj;
}
}
// current != null or another thread update
synchronized (current) {
return update(current, givenObj); // in place change of current
}

我的方案背后的关键思想+可靠性的"证明":

  1. 如果线程在不同的键上工作,则无需阻塞
  2. 如果currentnull,那么由于缓存是线程安全的,只有一个线程能够将对象放入缓存中,而其他线程将看到prev != null
  3. 其他线程必须按顺序更新。请注意,我正在同步current,要更新的对象。

问题

  1. 我的计划可靠吗?
  2. 可以优化吗?
  3. 在某些情况下,必须使用volatile来使内存同步可靠。我这里需要它吗?

谢谢!

1) 不,你的模式不可靠 你不应该打电话

cache.asMap().putIfAbsent(givenKey, givenObj);

通过番石榴文档方法cache.get(K键,可调用加载器)比使用asMap方法更可取。

2)是的,可以优化 您应该改为调用此方法:

cache.get(K key, Callable<? extends V> loader)

如果值已在缓存中,此方法将返回值,或者如果值不在缓存中,它将加载器中的值添加到缓存中并返回它。

所以例如:

MyObject objInCache =  cache.get(givenKey, ()->givenObj)
if(!objInCache.equals(givenobj)){
//obje was in the cache,
//update object
}

3)如果缓存是线程安全的,则不需要易失性

  1. 检查缓存中是否存在对象键
  2. 如果没有,则把
  3. 如果它已经在缓存中,则更新

这可以使用Map视图的计算方法完成。

cache.asMap().compute(givenKey, (key, oldValue) -> {
return (oldValue == null)
? create(key)
: update(current, oldValue);
});

这是线程安全的,应该允许并发计算不同的密钥。如果返回的新值null则删除映射,否则建立/更新映射。

不幸的是,这在 Guava 中没有优化,因为在添加 Java 8 兼容性时,它是在添加 Java 8 兼容性时被架起的。您应该更喜欢27.0.1或更新,因为有一些令人讨厌的错误。

Caffeine 是专为此功能设计的 Java 8 重写版。它建立在从 Guava(类似的界面,但重新审视一些设计选择)、现代算法研究和生态系统改进的经验教训之上。两者都很好,但你可能会发现咖啡因更适合更高级的场景。

最新更新