2009-06-26 32 views
2

我經常需要爲一些不經常更改的參考數據實現DAO。我有時在DA​​O的集合字段中緩存這個 - 所以它只加載一次,並在需要時明確更新。如何以線程安全的方式緩存DAO中的信息

但是,這帶來了許多併發問題 - 如果另一個線程在加載或更新時嘗試訪問數據會怎麼樣。

很明顯,這可以通過使數據的getter和setter同步來處理 - 但對於大型Web應用程序來說,這是相當的開銷。

我已經包含了一個我需要作爲稻草人所需的一個微不足道的例子。請建議替代方法來實現這一點。

public class LocationDAOImpl implements LocationDAO { 

private List<Location> locations = null; 

public List<Location> getAllLocations() { 
    if(locations == null) { 
     loadAllLocations(); 
    } 
    return locations; 
} 

有關進一步的信息,我使用Hibernate和Spring,但這個要求將適用於許多技術。

一些進一步的想法:

如果不是這種代碼在所有處理 - 而不是讓ehcache的或類似的處理呢? 有沒有這種我失蹤的常見模式? 顯然有很多方法可以實現,但我從來沒有找到簡單和可維護的模式。

在此先感謝!

+0

感謝所有的偉大的答案。每個添加的東西。 – Pablojim 2009-06-28 11:40:44

回答

2

如果您只是想快速翻閱自己的緩存解決方案,請參閱關於JavaSpecialist的this文章,該文章是對Java Concurrency in Practice的書Brian Goetz的評論。

它談論實現使用FutureTaskConcurrentHashMap基本線程安全的緩存。

做到這一點的方式確保只有一個線程同時觸發長時間運行的計算(在你的情況,你的數據庫在你的DAO調用)。

你不得不修改這個解決方案,如果你需要它添加緩存過期。

其他想法對自己的緩存是垃圾收集。如果你的緩存沒有使用WeakHashMap,那麼如果需要,GC將無法釋放緩存使用的內存。如果你緩存不經常訪問的數據(但由於數據很難計算,所以仍然值得高速緩存),那麼當你使用WeakHashMap時,你可能想在內存不足時幫助垃圾收集器。

6

最簡單和安全的方法是將ehcache library包含在您的項目中,並使用它來設置緩存。這些人已經解決了您可能遇到的所有問題,並且他們儘可能快地創建了圖書館。

1

如果您的參考數據是不可變的,那麼hibernate的二級緩存可能是一個合理的解決方案。

0

我認爲最好不要自己動手,因爲做對是件非常困難的事情。在Hibernate和Spring中使用EhCache或OSCache是​​一個更好的主意。

此外,它使得你的DAO有狀態,這可能是有問題的。除了Spring爲您管理的連接,工廠或模板對象之外,您應該沒有任何狀態。

更新:如果您的參考數據不是太大,並且確實永遠不會改變,那麼可能的替代設計是創建枚舉並完全放棄數據庫。沒有緩存,沒有休眠,沒有後顧之憂。或許oxbow_lakes的觀點值得考慮:或許它可能是一個非常簡單的系統。

+0

爲什麼你會打擾使用ehcache和hibernate之類的東西來構建一個非常簡單的系統?在我看來,添加依賴和重量級框架(如Hibernate)是一個重大決定。我學到了艱辛的道路,這關的,現成的方法可以回來咬你 – 2009-06-26 15:08:28

+0

他說,他已經在使用Hibernate的,所以它似乎是一個好主意,用的Ehcache比寫你自己的。是否使用Spring或Hibernate與編寫自己的問題是另一個問題。 – duffymo 2009-06-26 22:48:15

3

在我推出自己的參考數據緩存的情況下,我通常使用ReadWriteLock來減少線程爭用。我的每一個存取器然後採用以下形式:

public PersistedUser getUser(String userName) throws MissingReferenceDataException { 
    PersistedUser ret; 

    rwLock.readLock().lock(); 
    try { 
     ret = usersByName.get(userName); 

     if (ret == null) { 
      throw new MissingReferenceDataException(String.format("Invalid user name: %s.", userName)); 
     } 
    } finally { 
     rwLock.readLock().unlock(); 
    } 

    return ret; 
} 

以取出寫入鎖的唯一方法是refresh(),我通過一個MBean通常暴露:

public void refresh() { 
    logger.info("Refreshing reference data."); 
    rwLock.writeLock().lock(); 
    try { 
     usersById.clear(); 
     usersByName.clear(); 

     // Refresh data from underlying data source. 

    } finally { 
     rwLock.writeLock().unlock(); 
    } 
} 

順便提及,我選擇實施我自己的緩存,因爲:

  • 我的參考數據集合很小,所以我可以隨時將它們全部存儲在內存中。
  • 我的應用程序需要簡單/快速;我希望儘可能少地依賴外部庫。
  • 的數據很少更新,當它是調用刷新()是相當快的。因此,我急切地初始化我的緩存(與您的稻草人示例不同),這意味着訪問者永遠不需要取出寫入鎖。
0

顯然,這可以通過使雙方的getter和同步數據的制定者來處理 - 但對於大型Web應用程序,這是相當的開銷。

我已經包含了一個我需要作爲稻草人所需的一個微不足道的例子。請建議替代方法來實現這一點。

雖然這可能是有些真實的,你應該注意,你肯定能夠提供的示例代碼需要同步,以避免任何併發問題時延遲加載的locations。如果訪問不同步,那麼你將有:

  • 多個線程在同一時間
  • 某些線程可能進入loadAllLocations()訪問loadAllLocations()方法的另一個線程完成的方法和結果分配給locations即使 - 在Java內存模型下,不保證其他線程在沒有同步的情況下會看到變量的變化。

使用延遲加載/初始化時要小心,它看起來像一個簡單的性能提升,但它可能會導致很多討厭的線程問題。

+0

謝謝馬特 - 我意識到它被打破了,這就是爲什麼我將其稱爲有缺陷的原因。 – Pablojim 2009-06-26 14:10:06

相關問題