我经常需要为一些不经常变化的参考数据实现DAO。我有时会在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 :(得分:6)
最简单和安全的方法是在项目中包含ehcache library并使用它来设置缓存。这些人已经解决了您可能遇到的所有问题,并且他们尽可能快地建立了图书馆。
答案 1 :(得分: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();
}
}
顺便说一句,我选择实现自己的缓存,因为:
答案 2 :(得分:2)
如果您只想快速推送自己的缓存解决方案,请查看有关JavaSpecialist的this文章,该文章是Java Concurrency in Practice对Brian Goetz一书的审核。
它讨论了使用FutureTask和ConcurrentHashMap实现基本线程安全缓存。
这样做的方法可确保只有一个并发线程触发长时间运行的计算(在您的情况下,您的数据库在DAO中调用)。
如果需要,您必须修改此解决方案以添加缓存过期。
另一个关于自己缓存的想法是垃圾收集。如果不使用WeakHashMap作为缓存,那么如果需要,GC将无法释放缓存使用的内存。如果您正在缓存不常访问的数据(但由于难以计算,仍然值得缓存的数据),那么您可能希望在使用WeakHashMap运行内存不足时帮助垃圾收集器。
答案 3 :(得分:1)
如果你的参考数据是不可变的,那么hibernate的二级缓存可能是一个合理的解决方案。
答案 4 :(得分:0)
我认为最好不要自己动手,因为做正确是一件非常困难的事情。使用EhCache或OSCache与Hibernate和Spring是一个好主意。
此外,它会使您的DAO状态,这可能会有问题。除了Spring为您管理的连接,工厂或模板对象之外,您应该没有任何状态。
更新:如果你的参考数据不是太大,而且真的永远不会改变,那么另一种设计可能是创建枚举并完全省去数据库。没有缓存,没有Hibernate,没有后顾之忧。也许oxbow_lakes的观点值得考虑:也许它可能是一个非常简单的系统。
答案 5 :(得分:0)
显然,这可以通过使数据的getter和setter同步来处理 - 但是对于大型Web应用程序来说,这是一个相当大的开销。
我已经包含了一个琐碎的例子,说明了我作为一个稻草人所需要的东西。请提供其他实施方法。
虽然这可能有点真实,但您应该注意,您提供的示例代码当然需要进行同步,以避免在延迟加载locations
时出现任何并发问题。如果该访问者未同步,那么您将拥有:
loadAllLocations()
方法loadAllLocations()
,某些线程也可能进入locations
- 在Java内存模型下,无法保证其他线程会看到变量中的变化没有同步。使用延迟加载/初始化时要小心,这似乎是一个简单的性能提升,但它可能会导致许多讨厌的线程问题。