2

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

提前致谢!

4

6 回答 6

6

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

于 2009-06-26T09:41:56.823 回答
3

在我滚动了自己的参考数据缓存的情况下,我通常使用 aReadWriteLock来减少线程争用。然后我的每个访问者都采用以下形式:

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() 时相当快。因此,我急切地初始化了我的缓存(与您的稻草人示例不同),这意味着访问者永远不需要取出写锁。
于 2009-06-26T10:12:10.427 回答
2

如果您只想快速推出自己的缓存解决方案,请查看JavaSpecialist上的这篇文章,这是对Brian Goetz的Java Concurrency in Practice一书的评论。

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

这样做的方式确保只有一个并发线程触发长时间运行的计算(在您的情况下,您的 DAO 中的数据库调用)。

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

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

于 2009-06-26T14:23:33.433 回答
1

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

于 2009-06-26T09:42:22.127 回答
0

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

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

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

于 2009-06-26T09:59:46.740 回答
0

显然,这可以通过使数据的 getter 和 setter 同步来处理 - 但对于大型 Web 应用程序来说,这是相当大的开销。

我已经包含了一个微不足道的有缺陷的例子,说明我作为稻草人需要什么。请提出替代方法来实现这一点。

虽然这可能有点正确,但您应该注意,您提供的示例代码肯定需要同步,以避免在延迟加载locations. 如果该访问器未同步,那么您将拥有:

  • 多个线程同时访问loadAllLocations()方法
  • loadAllLocations()即使在另一个线程完成该方法并将结果分配给之后,一些线程也可能进入locations- 在 Java 内存模型下,不能保证其他线程会在没有同步的情况下看到变量的变化。

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

于 2009-06-26T12:50:37.160 回答