考虑一下
public Object doGet() {
return getResource();
}
private Object getResource() {
synchronized (lock) {
if (cachedResourceIsStale()) {
downloadNewVersionOfResource();
}
}
return resource;
}
假设 doGet
将并发执行,并且执行很多,并且下载新版本的资源需要一段时间,是否有更有效的方法在 中进行同步获取资源
?我知道读/写锁,但我认为它们不能在这里应用。
为什么要同步?如果缓存过时,所有访问该资源的线程仍由第一个线程刷新时,将执行它们自己的刷新。在这导致的其他问题中,它几乎没有效率。
正如 BalusC 在评论中提到的,我目前在一个 servlet 中面临这个问题,但我很高兴得到通用的答案,因为谁知道我会在什么情况下再次遇到它。
最佳答案
假设
- 高效意味着
doGet()
应该尽快完成 cachedPageIsStale()
完全不需要时间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/