java - 高效的缓存同步

标签 java multithreading servlets synchronization locking

考虑一下

public Object doGet() {
    return getResource();
}

private Object getResource() {
    synchronized (lock) {
        if (cachedResourceIsStale()) {
            downloadNewVersionOfResource();
        }
    }

    return resource;
}

假设 doGet 将并发执行,并且执行很多,并且下载新版本的资源需要一段时间,是否有更有效的方法在 中进行同步获取资源?我知道读/写锁,但我认为它们不能在这里应用。

为什么要同步?如果缓存过时,所有访问该资源的线程仍由第一个线程刷新时,将执行它们自己的刷新。在这导致的其他问题中,它几乎没有效率。

正如 BalusC 在评论中提到的,我目前在一个 servlet 中面临这个问题,但我很高兴得到通用的答案,因为谁知道我会在什么情况下再次遇到它。

最佳答案

假设

  1. 高效意味着 doGet() 应该尽快完成
  2. cachedPageIsStale() 完全不需要时间
  3. downloadNewVersionOfResource() 需要一点时间

回答

同步减少了网络负载,因为只有一个线程在过期时获取资源。此外,它不会过度延迟其他线程的处理 - 由于 VM 不包含线程可以返回的当前快照,它们将不得不阻塞,并且没有理由额外的并发 downloadNewVersionOfResource() 会完成得更快(由于网络带宽争用,我希望相反)。

所以同步是好的,并且在带宽消耗和响应时间方面是最优的。 (与 I/O 等待相比,同步的 CPU 开销微乎其微)——假设调用 doGet() 时资源的当前版本可能不可用;如果您的服务器始终拥有资源的当前版本,它可以立即将其发回。 (您可能有一个后台线程在旧版本到期之前下载新版本。)

附言

您还没有显示任何错误处理。您必须决定是将 downloadNewVersionOfResource() 抛出的异常传播给您的调用者,还是继续提供旧版本的资源。

编辑

所以呢?假设您有 100 个 connection worker,检查资源是否过时需要一微秒,资源是否过时并为它提供服务需要一秒钟。然后,平均有 100 * 10^-6/1 = 0.0001 个线程正在尝试获取锁。几乎没有任何争论。获取未占用锁的开销大约为 10^-8 秒。优化已经需要微发送的东西是没有意义的,因为网络会导致毫秒级的延迟。如果您不相信我,请为同步做一个微基准测试。的确,频繁的、不必要的同步会增加大量开销,因此不推荐使用同步集合类。但那是因为这些方法每次调用只做很少的工作,而同步的相对开销要大得多。我刚刚为以下代码做了一个小的微基准测试:

synchronized (lock) {
    c++;
}

在我的笔记本上,这需要 50 纳秒(5*10^-8 秒),在 sun 的热点虚拟机中平均执行超过 1000 万次。这大约是裸增量操作的 20 倍,因此如果执行大量增量操作,则同步每个增量操作会使程序减慢一个数量级。但是,如果该方法确实阻塞了 I/O,例如等待 1 毫秒,那么添加同样的 50 纳秒将使吞吐量降低 0.005%。当然,您有更好的性能调整机会:-)

这就是为什么您应该始终在开始优化之前进行测量。它可以防止您投入数小时的时间来节省几纳秒的处理器时间。

关于java - 高效的缓存同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4017437/

相关文章:

java - 如何使用ANT脚本检查JBOSS版本

java - MouseListener 和 JTree

java - 在eclipse中运行tomcat上的maven项目时找不到请求资源404错误

java - 如何在jsp中填充java对象值

java - 让Tomcat查看子文件夹

java - 失败:SemanticException [错误10014]:行1:21错误的参数 'stock_price_high':

c++ - QThreadPool maxThreadCount 在应用程序和 DLL 中不同

c# - 如何保证 Array 中 "reference type"项目的更新对其他线程可见?

c# - Random.Next() 有时会在不同的线程中返回相同的数字

java - javax.servlet 在哪里?