刷新缓存而不影响访问缓存的延迟

时间:2018-02-28 01:57:17

标签: java multithreading caching

我有一个缓存刷新逻辑,并希望确保它是线程安全且正确的方法。

public class Test {

    Set<Integer> cache = Sets.newConcurrentHashSet();

    public boolean contain(int num) {
        return cache.contains(num);
    }

    public void refresh() {
        cache.clear();
        cache.addAll(getNums());
    }
}

所以我有一个后台线程刷新缓存 - 定期调用refresh。并且多个线程同时调用contain。我试图避免在方法签名中使用synchronized,因为refresh可能需要一些时间(假设getNum进行网络调用并解析大量数据),然后contain将被阻止

我认为此代码不够好,因为如果在containclear之间调用addAll,则contain始终返回false。

实现缓存刷新的最佳方法是什么,而不会影响contain调用的重要延迟?

3 个答案:

答案 0 :(得分:3)

最好的方法是使用函数式编程范例,你有不可变状态(在这种情况下是Set),而不是向该集添加和删除元素,每次都创建一个全新的Set你想添加或删除元素。这是在Java9中。

然而,为遗留代码实现此方法可能有点尴尬或不可行。所以你可以做的就是有2 Sets 1,其上有get方法,它是volatile,然后在refresh方法中为它分配了一个新实例。

public class Test {

    volatile Set<Integer> cache = new HashSet<>();

    public boolean contain(int num) {
        return cache.contains(num);
    }

    public void refresh() {
        Set<Integer> privateCache = new HashSet<>();
        privateCache.addAll(getNums());
        cache = privateCache;
    }
}

修改我们不想要或不需要ConcurrentHashSet,也就是说你想同时在一个集合中添加和删除元素,这在我看来是个漂亮的无用的事情要做。但是你想用新的Set切换旧的Require Import Coq.Bool.Bool. Goal forall b, b = true. intros. (* Create a large goal *) do 300 rewrite <- orb_false_r with (b := b). Time do 300 try idtac. (* Finished transaction in 0.001 secs (0.004u,0.s) (successful) *) Time do 300 try match goal with |- context [_ || true] => idtac end. (* Finished transaction in 0.108 secs (0.108u,0.s) (successful) *) Time do 300 try match goal with |- context [_ || ?b] => constr_eq b true end. (* Finished transaction in 3.21 secs (3.208u,0.s) (successful) *) Time do 300 try rewrite orb_true_r. (* Finished transaction in 2.862 secs (2.863u,0.s) (successful) *) ,这就是为什么你只需要一个volatile变量来确保你不能同时读取和编辑缓存。

但正如我在一开始的回答中提到的那样,如果你从不修改集合,而是每次想要更新集合时再创建新集合(请注意,这是一个非常便宜的操作,因为内部旧集合被重用在操作中)。这样您就不必担心并发性,因为线程之间没有共享状态。

答案 1 :(得分:0)

在调用contains时,如何确保缓存不包含无效条目?此外,每次getNums()更改时都需要调用刷新,这非常低效。最好是确保控制对getNums()的更改,然后相应地更新缓存。缓存可能如下所示:

const itemsArray = list.map((item)=>{
     if(item.done){
       count+=1;
     }
     return (
     <div> <li 
      key={item.index}
      style={item.done?styleItem:null}
      onClick={(e)=> this.props.toggleTodos(e,item.index) }
      >{item.value} 
      </li>
       <span 
       style={removeStyle}
       onClick={(e)=> this.props.removeTodos(e,item.index)}
       >x</span>
       </div>
     )

   })

答案 2 :(得分:-1)

更新

正如@schmosel让我意识到的那样,我的努力是浪费的:实际上足以用HashSet<>方法中的值初始化一个全新的refresh。当然假设缓存标有volatile。简而言之,@ Snickers3192的答案,指出你所寻求的。

旧答案

您也可以使用略有不同的系统。

保留两个Set<Integer>,其中一个永远为空。刷新cache时,可以异步重新初始化第二个,然后只需切换指针。访问缓存的其他线程将不会看到任何特定的开销。

从外部角度来看,他们将始终访问相同的cache

private volatile int currentCache; // 0 or 1

private final Set<Integer> caches[] = new HashSet[2]; // use two caches; either one will always be empty, so not much memory consumed

private volatile Set<Integer> cachePointer = null; // just a pointer to the current cache, must be volatile

// initialize
{
    this.caches[0] = new HashSet<>(0);
    this.caches[1] = new HashSet<>(0);

    this.currentCache = 0;

    this.cachePointer = caches[this.currentCache]; // point to cache one from the beginning
}

您的刷新方法可能如下所示:

public void refresh() {

    // store current cache pointer
    final int previousCache = this.currentCache;
    final int nextCache = getNextPointer();

    // you can easily compute it asynchronously
    // in the meantime, external threads will still access the normal cache
    CompletableFuture.runAsync( () -> {
        // fill the unused cache
        caches[nextCache].addAll(getNums());
        // then switch the pointer to the just-filled cache
        // from this point on, threads are accessing the new cache
        switchCachePointer();
        // empty the other cache still on the async thread
        caches[previousCache].clear();
    });
}

实用程序方法是:

public boolean contains(final int num) {
    return this.cachePointer.contains(num);
}

private int getNextPointer() {
    return ( this.currentCache + 1 ) % this.caches.length;
}

private void switchCachePointer() {
    // make cachePointer point to a new cache
    this.currentCache = this.getNextPointer();
    this.cachePointer = caches[this.currentCache];

}