如何从多个线程更新缓存

时间:2018-11-29 14:41:02

标签: java multithreading caching

我有一个具有缓存(类型为Cache)的Runnable,我们假定它提供了线程安全的操作。此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使内存同步可靠。我在这里需要它吗?

谢谢!

2 个答案:

答案 0 :(得分:1)

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)如果缓存是线程安全的,则不需要volatile

答案 1 :(得分:1)

  
      
  1. 检查对象密钥是否存在于缓存中
  2.   
  3. 如果没有,那么放
  4.   
  5. 如果它已经在缓存中,则进行更新
  6.   

这可以使用Map视图的compute方法来实现。

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

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

不幸的是,这在Guava中并未进行优化,因为它在添加Java 8兼容性时已被搭建。您应该选择27.0.1或更高版本,因为其中存在一些令人讨厌的错误。

Caffeine是为此功能而设计的Java 8重写。它基于从Guava(相似的界面,但重新审视了一些设计选择),现代算法研究和生态系统改进中获得的经验教训。两者都很出色,但是您可能会发现咖啡因更适合更高级的情况。