2011-07-29 60 views
0

我正在編寫一個緩存實現 - 如果它在商店中已超過5分鐘,它會使存儲的項目過期。在這種情況下,它應該從源刷新,否則應該返回緩存的副本。Java緩存設計問題

下面是我寫的 - 有沒有任何設計缺陷?特別是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(); 
} 

}

+2

我寧願System.currentTimeMillis的()在新的Date()的getTime( –

+0

如果您避風港」 t已經這麼做了,你可能想要發佈到[代碼評論](http://codereview.stackexchange.com/),這是針對這些問題。 – ig0774

+1

不是一個真正的答案,但....如果這不是家庭作業,你真的不應該手工做這件事。在許多經過充分測試的實現中,緩存是一個解決的問題,它們是免費且開源的。我真的希望這只是一個工程練習。否則,你只是在尋求麻煩。 – rfeak

回答

1

既然你要的東西手動同步,並(在猜測),你似乎並沒有很全面的測試,我會說有大約98%的機會,你在那裏有一個錯誤。您是否有充分的理由不使用已建立的緩存庫提供的功能,如Ehcache的SelfPopulatingCache

+0

假設一會兒 - 外部庫的使用受到限制。儘管我做了一些基本的Junit測試 - 它通過了這些測試。 – Bhaskar

0

文檔說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(); 
} 
+0

@Bhaskar:'getItemFromSource'不在同步塊中。 – MRAB

+0

使用同步塊的關鍵不在於刪除或替換,而是確保在時間檢查和實際修改之間 - 沒有其他線程可以修改該集合。 – Bhaskar

+1

兩個線程都可以獲得相同的過期項目,從源重新獲取並替換它。我不認爲會導致錯誤的結果,但更多的工作比必要的,特別是如果'getItemFromSource()'是資源密集型的,我認爲這是緩存的原因。 –