Java 如何以线程安全的方式在DAO中缓存信息

Java 如何以线程安全的方式在DAO中缓存信息,java,caching,concurrency,dao,Java,Caching,Concurrency,Dao,我经常需要为一些不经常更改的参考数据实现DAO。我有时会将其缓存在DAO的集合字段中,以便只加载一次,并在需要时显式更新 但是,这会带来许多并发问题—如果另一个线程在加载或更新数据时尝试访问该数据会怎么样 显然,这可以通过同步数据的getter和setter来处理,但是对于大型web应用程序来说,这是一个相当大的开销 我已经包括了一个小的有缺陷的例子,我需要作为一个救命稻草。请提出实施这一点的替代方法 public class LocationDAOImpl implements Locatio

我经常需要为一些不经常更改的参考数据实现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;
}
public类LocationDAOImpl实现LocationDAO{
私有列表位置=空;
公共列表getAllLocations(){
如果(位置==null){
loadAllLocations();
}
返回地点;
}
关于更多信息,我将使用Hibernate和Spring,但这一要求将适用于许多技术

进一步思考:

这是否应该根本不在代码中处理——而是让ehcache或类似的工具来处理? 有没有一个共同的模式,我错过了? 显然有很多方法可以实现这一点,但我从未找到一种简单且可维护的模式


提前感谢!

最简单和安全的方法是将包含在项目中,并使用它来设置缓存。这些人已经解决了您可能遇到的所有问题,他们已经尽可能快地创建了库。

如果您的参考数据是不可变的,hibernate的二级缓存可能是一个合理的解决方案。

我认为最好不要自己去做,因为要把它做好是一件非常困难的事情。将EhCache或OSCache与Hibernate和Spring结合使用是一个更好的主意

此外,它使DAO有状态,这可能会有问题。除了Spring为您管理的连接、工厂或模板对象之外,您应该没有任何状态


更新:如果你的参考数据不是太大,而且永远不会改变,也许另一种设计是创建枚举,完全不用数据库。没有缓存,没有休眠,不用担心。也许oxbow_lakes的观点值得考虑:也许它可能是一个非常简单的系统。

在我滚动了在自己的引用数据缓存中,我通常使用
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();
    }
}
顺便说一句,我选择实现自己的缓存是因为:

  • 我的参考数据集很小,所以我可以将它们全部存储在内存中
  • 我的应用程序需要简单/快速;我希望对外部库的依赖性尽可能少
  • 数据很少更新,而且调用refresh()时速度相当快。因此,我急切地初始化缓存(与你的Strawman示例不同),这意味着访问器永远不需要取出写锁
显然,这可以通过同步数据的getter和setter来处理,但是对于大型web应用程序来说,这是一个相当大的开销

我已经包括了一个小的有缺陷的例子,说明了我作为一个稻草人所需要的东西。请建议其他方法来实现这一点

虽然这在某种程度上可能是正确的,但您应该注意,您提供的示例代码当然需要同步,以避免延迟加载
位置时出现任何并发问题。如果访问器未同步,则您将有:

  • 多个线程同时访问
    loadAllLocations()
    方法
  • 某些线程可能会输入
    loadAllLocations()
    ,即使在另一个线程完成该方法并将结果分配给
    locations
    ——在Java内存模型下,无法保证其他线程会在不同步的情况下看到变量中的更改

在使用延迟加载/初始化时要小心,这似乎是一个简单的性能提升,但它可能会导致许多严重的线程问题。

如果您只想快速推出自己的缓存解决方案,请看一篇关于JavaSpecialist的文章,这篇文章是作者对本书的回顾

它讨论了如何使用和实现基本的线程安全缓存

这样做可以确保只有一个并发线程触发长时间运行的计算(在您的例子中,您的数据库在DAO中调用)

如果需要,您必须修改此解决方案以添加缓存到期


另一种自己缓存的想法是垃圾收集。如果不为缓存使用WeakHashMap,GC将无法在需要时释放缓存使用的内存。如果缓存的是不经常访问的数据(但由于难以计算,仍然值得缓存的数据),那么您可能希望在内存不足时使用WeakHashMap来帮助垃圾收集器。

谢谢Matt-我意识到它已经坏了,这就是为什么我将其称为有缺陷的原因。您为什么要为可能非常简单的系统使用ehcache和hibernate之类的东西?添加依赖项和如此重的框架(像冬眠一样)在我看来,这是一个重大的决定。我学到了这种现成的方法可能会反过来伤害你的艰难方法。他说他已经在使用Hibernate,因此使用EhCache似乎比编写自己的EhCache更好。是否使用Spring或Hibernate与编写自己的EhCache是另一个问题。感谢所有伟大的answers.每个都添加了一些东西。我从中学到的最多,因此被接受。在某些情况下,使用开箱即用的缓存解决方案是不够的。