Java缓存设计问题

Java缓存设计问题,java,class,Java,Class,我在写一个缓存实现——如果存储项在存储中的时间超过5分钟,它就会过期。在这种情况下,应该从源刷新它,否则应该返回缓存副本 下面是我写的-它有任何设计缺陷吗?特别是get部分 public class Cache<K,V> { private final ConcurrentMap<K,TimedItem> store ; private final long expiryInMilliSec ; Cache(){ store

我在写一个缓存实现——如果存储项在存储中的时间超过5分钟,它就会过期。在这种情况下,应该从源刷新它,否则应该返回缓存副本

下面是我写的-它有任何设计缺陷吗?特别是
get
部分

public class Cache<K,V> {
     private final ConcurrentMap<K,TimedItem> store ;
     private final long expiryInMilliSec ;


    Cache(){
        store = new ConcurrentHashMap<K, TimedItem>(16);
         expiryInMilliSec = 5 * 60 * 1000; // 5 mins
     }

    Cache(int minsToExpire){
        store = new ConcurrentHashMap<K, TimedItem>(16);
        expiryInMilliSec = minsToExpire * 60 * 1000; 
     }

// Internal class to hold item and its 'Timestamp' together
private class TimedItem {
    private long timeStored ;
    private V item ;

    TimedItem(V v) {
        item = v;
        timeStored = new Date().getTime();
    }

    long getTimeStored(){
        return timeStored;
    }

    V getItem(){
        return item;
    }

    public String toString(){
        return item.toString();
    }
}

// sync on the store object - its a way to ensure that it does not interfere
// with the get method's logic below
public void put(K key, V item){
    synchronized(store){
        store.putIfAbsent(key, new TimedItem(item));
    }
}

// Lookup the item, check if its timestamp is earlier than current time 
// allowing for the expiry duration
public V get(K key){
    TimedItem ti = null;
    K keyLocal = key;
    boolean refreshRequired = false;

    synchronized(store){
        ti = store.get(keyLocal);
        if(ti == null)
            return null;
        long currentTime = new Date().getTime();
        if( (currentTime - ti.getTimeStored()) > expiryInMilliSec ){
            store.remove(keyLocal);
            refreshRequired = true;
        }
    }
    // even though this is not a part of the sync block , this should not be a problem
    // from a concurrency point of view
    if(refreshRequired){
        ti = store.putIfAbsent(keyLocal, new TimedItem(getItemFromSource(keyLocal)) );
    }
    return ti.getItem();
}

private V getItemFromSource(K key){
    // implement logic for refreshing item from source 
    return null ;  
}

public String toString(){
    return store.toString();
}
公共类缓存{
私有最终ConcurrentMap商店;
私人最终长时间有效期为毫秒;
缓存(){
存储=新的ConcurrentHashMap(16);
expiryin毫秒=5*60*1000;//5分钟
}
缓存(int minsToExpire){
存储=新的ConcurrentHashMap(16);
expiryin毫秒=minsToExpire*60*1000;
}
//将项及其“时间戳”保存在一起的内部类
私有类时间数据项{
私人长时间存储;
私人物品;
TimedItem(V){
项目=v;
timeStored=新日期().getTime();
}
长getTimeStored(){
返回时间存储;
}
V getItem(){
退货项目;
}
公共字符串toString(){
return item.toString();
}
}
//在store对象上同步-这是一种确保它不会干扰的方法
//使用下面的get方法逻辑
公开作废(K键,V项){
已同步(存储){
store.putIfAbsent(键,新的TimedItem(项));
}
}
//查找项目,检查其时间戳是否早于当前时间
//考虑到到期期限
公共V get(K键){
TimedItem ti=null;
K keyLocal=密钥;
布尔值refreshRequired=false;
已同步(存储){
ti=store.get(keyLocal);
如果(ti==null)
返回null;
长currentTime=新日期().getTime();
如果((currentTime-ti.getTimeStored())>ExpiryIn毫秒){
储存。移除(本地);
refreshRequired=true;
}
}
//即使这不是同步块的一部分,这也不应该是问题
//从并发的角度来看
如果需要(刷新){
ti=store.putIfAbsent(keyLocal,new TimedItem(getItemFromSource(keyLocal));
}
返回ti.getItem();
}
private V getItemFromSource(K密钥){
//实现从源代码刷新项的逻辑
返回null;
}
公共字符串toString(){
return store.toString();
}

}

鉴于您试图手动同步,而且(据猜测)您似乎没有对其进行彻底测试,我想说您有98%的机会在其中出现错误。您不使用已建立的缓存库(如Ehcache)提供的功能有什么好的原因吗?

文档中说,
replace
是原子的,所以我会这样做:

public V get(K key){
    TimedItem ti;

    ti = store.get(key);
    if(ti == null)
        return null;

    long currentTime = new Date().getTime();
    if((currentTime - ti.getTimeStored()) > expiryInMilliSec){
        ti = new TimedItem(getItemFromSource(key));
        store.replace(key, ti);
    }

    return ti.getItem();
}

我更喜欢System.currentTimeMillis()而不是new Date().getTime()如果你还没有这样做,你可能想把这篇文章发到,这是针对这类问题的。这不是一个真正的答案,但是。。。。如果这不是家庭作业,你真的不应该手工做。缓存是许多经过良好测试的免费开源实现所解决的问题。我真的希望这只是一个工程实践。否则,您只是自找麻烦。您不应该使用
synchronized(store)
,而应该定义一些专用的
对象锁。如果
store
也将自身用作同步锁(例如,通过在
ConcurrentHashMap
中定义同步方法),事情可能会变得非常混乱(死锁)。顺便说一句,我检查了代码,在Oracle Java 7 ConcurrentHashMap的实现中,HashMap本身不同步。不过我做了一些基本的Junit测试,它通过了这些测试。@Bhaskar:
getItemFromSource
不在同步块中。使用同步块的意义不在于删除或替换,但是为了确保在时间检查和实际修改之间没有其他线程可以修改集合,两个线程都可以获取相同的过期项目,从源代码重新获取并替换它。我不认为这会导致错误的结果,但需要做更多的工作,特别是如果
getItemFromSource()
是资源密集型的,我认为这就是缓存的原因。@Dave:是的,尽管这是否是一个问题,这取决于实际的成本。成本是否足够高,重复抓取是否足够频繁,以证明更复杂的解决方案的合理性?