并发环境下,getFile方法的#3与#4实现哪个性能更优?原因是什么?
并发场景下缓存获取方法的性能对比:#3 vs #4
背景与代码回顾
我们有一个文件缓存系统:本地有文件则从本地读取,否则从Web下载并缓存(假设下载和缓存的读写逻辑已实现,单URL对应单请求)。
初始单线程版本(#1)
public InputStream getFile(String url) { InputStream retStream = getFileFromLocalFS(url); if (retStream != null) { return retStream; } else { retStream = getFileFromLocalFS(url); if (retStream == null) { return getFileFromWeb(url); } } return retStream; }
全方法同步版本(#2)
为支持并发且避免重复下载,给整个方法加了synchronized,但导致所有请求串行,即使本地有文件也无法并行读取:
public synchronized InputStream getFile(String url) { InputStream retStream = getFileFromLocalFS(url); if (retStream != null) { return retStream; } else { retStream = getFileFromLocalFS(url); if (retStream == null) { return getFileFromWeb(url); } } return retStream; }
面试官提出的双重同步版本(#3)
试图通过局部同步块优化,但保留了方法级同步:
public synchronized InputStream getFile(String url) { InputStream retStream = getFileFromLocalFS(url); if (retStream != null) { return retStream; } else { synchronized (this) { retStream = getFileFromLocalFS(url); if (retStream == null) { return getFileFromWeb(url); } } } return retStream; }
移除方法级同步的版本(#4)
去掉方法级同步,仅在本地未命中时进入同步块:
public InputStream getFile(String url) { InputStream retStream = getFileFromLocalFS(url); if (retStream != null) { return retStream; } else { synchronized (this) { retStream = getFileFromLocalFS(url); if (retStream == null) { return getFileFromWeb(url); } } } return retStream; }
性能对比分析
结论:#4的性能远优于#3
原因如下:
#3的方法级同步完全抵消了局部同步块的优化
方法级synchronized等价于在整个方法体外包裹synchronized(this),所以#3的所有请求都必须先获取this锁才能执行任何逻辑——不管目标文件是否在本地缓存,所有线程都得串行执行,和#2的性能没有区别。内部的嵌套同步块只是同一线程的重入锁,没有任何实际优化作用,完全是多余的。#4真正实现了锁细化,适配缓存的高频/低频路径
- 高频路径(本地有文件):无锁执行
getFileFromLocalFS,所有线程可以并行读取本地缓存,这是缓存场景下的主流情况,能极大提升并发吞吐量。 - 低频路径(本地无文件):仅这部分请求进入同步块做二次检查,确保同一URL只会触发一次Web下载,即使串行也不会影响整体性能。
- 高频路径(本地有文件):无锁执行
内容的提问来源于stack exchange,提问作者Bill The Ape
相关产品推荐
相关产品推荐

