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

时间:2009-06-26 09:38:55

标签: java caching concurrency dao

我经常需要为一些不经常变化的参考数据实现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或类似处理吗? 我缺少一个共同的模式吗? 显然有很多方法可以实现,但我从未找到过简单易维护的模式。

提前致谢!

6 个答案:

答案 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();
    }
}

顺便说一句,我选择实现自己的缓存,因为:

  • 我的参考数据集很小,所以我总是可以将它们全部存储在内存中。
  • 我的应用需要简单/快速;我希望尽可能少地依赖外部库。
  • 数据很少更新,而且只需调用refresh()即可。因此,我急切地初始化我的缓存(与你的稻草人示例不同),这意味着访问者永远不需要取出写锁。

答案 2 :(得分:2)

如果您只想快速推送自己的缓存解决方案,请查看有关JavaSpecialist的this文章,该文章是Java Concurrency in PracticeBrian Goetz一书的审核。

它讨论了使用FutureTaskConcurrentHashMap实现基本线程安全缓存。

这样做的方法可确保只有一个并发线程触发长时间运行的计算(在您的情况下,您的数据库在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内存模型下,无法保证其他线程会看到变量中的变化没有同步。

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