我有一个具有缓存(缓存类型)的 Runnable,我们假设它提供线程安全操作。此可运行对象由多个线程使用。
我们的线程从外部源获取对象,然后
- 检查缓存中是否存在对象键
- 如果没有,则把
- 如果它已经在缓存中,则更新
我正在寻找正确的方案(即最小的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
}
我的方案背后的关键思想+可靠性的"证明":
- 如果线程在不同的键上工作,则无需阻塞
- 如果
current
是null
,那么由于缓存是线程安全的,只有一个线程能够将对象放入缓存中,而其他线程将看到prev != null
- 其他线程必须按顺序更新。请注意,我正在同步
current
,要更新的对象。
问题
- 我的计划可靠吗?
- 可以优化吗?
- 在某些情况下,必须使用
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)如果缓存是线程安全的,则不需要易失性
- 检查缓存中是否存在对象键
- 如果没有,则把
- 如果它已经在缓存中,则更新
这可以使用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(类似的界面,但重新审视一些设计选择)、现代算法研究和生态系统改进的经验教训之上。两者都很好,但你可能会发现咖啡因更适合更高级的场景。