2016-02-05 38 views
2

我想实现一个简单的缓存,它会定期更新,并且每个更新都会触发完全缓存清除和数据插入。对java同步缓存的批量操作

伪代码:

//context calls periodically this method 
cache.clear(); 
cache.putAll(newValues) 

由于其他线程可能在刷新操作过程中读取缓存。我需要某种同步。

最简单的解决方案可能是类似以下内容:

computeNewCacheValues() 
computeStaleKeys() //e.g. ones are in the cache but are not in the new cache 
removeStaleKeysOneByOneFromCache() 
updateKeysFromNewCacheValueOneByOne() 

实现由ConccurentHashMap实例提供支持 - 这样的高速缓存中的更新:发生

  • 没有并发问题
  • (?)
  • 整个过程中缓存未锁定(因此在刷新期间可访问)

这可能是一个很好的解决方案,但我想知道:还有其他更实用的方法吗?有没有能够进行这种操作的图书馆?

+0

在您的示例中,您没有用新值覆盖现有项目,所以它们永远不会改变?那么如果你可以随着时间的推移填充缓存,那么如此复杂的替换又有什么意义呢? – AdamSkywalker

+0

更新addKeysFromNeCacheValueOneByOne以更新KeeFromNewCacheValueOneBeOne;谢谢! – krisy

+0

那么彼得的答案就好了,原子替代整个缓存要比这些复杂的更新技巧更好 – AdamSkywalker

回答

2

如果您总是替换整个缓存,您可以将其替换。

final AtomicReference<Map<K, V>> mapRef = new AtomicReference<>(); 

// assuming you don't modify the map after calling this. 
public void update(Map<K, V> map) { 
    mapRef.set(map); 
} 

public V get(K key) { 
    // this will always see the latest complete map. 
    return mapRef.get().get(key); 
} 

注意:不需要锁定,因为一旦将Map添加到缓存中,Map就不会被更改。

+0

当调用mapRef.get()。get(key)时,这是否意味着将返回一个map对象的副本 - 因此每次调用在得到做复制操作?当两个获取操作同时被调用时会发生什么? – krisy

+0

@ krisy每次都需要一个典型4字节的*引用*副本。 Map对象不被复制。如果多个线程一次调用它们,它们将分别具有对映射的引用副本,但在内存中将只有一个映射副本。注意:这个Map不能被修改,因为这个没有锁。当您更新缓存时,您必须创建一个新的地图,例如每次都有一个HashMap。这个新地图可以基于旧地图的副本。 –

+0

最后一个问题;在AtomicReference类的(J8)源代码中,我看到在引擎盖下使用了volatile变量。据我所知,所有对volatile变量的访问都像这些访问同步一样。因此,当两种方法同时调用mapRef.get()时,这是否意味着它们将具有顺序访问,例如,一个线程将暂停,直到另一个线程结束? – krisy

0

以供将来参考:

当创建一个“bulk cache”,最简单的解决方案可能是@Peter提到的一个;在单独的线程上创建新的缓存,并将旧的参考更改为新的缓存。

需要考虑的事情:

  • 解决方案的工作,因为赋值运算符( “=”)执行的是原子
  • 缓存对象必须是volatile;这样它的值可以被多个线程访问(没有这个线程可能会改变它的值,但另一个线程将不能看到改变)
  • AtomicReference是另一种选择;虽然它增加了一些有用的方法(例如用于审计)