ReentrantLock锁块内多次volatile读取可否替换为单次本地变量读取
结论
你完全可以将references字段单次读取到本地变量缓存,这种改写不仅不会违反JMM规范,还能减少volatile读带来的内存屏障开销,提升执行效率。
原因分析
- 你代码中的所有读写操作都发生在
lock()成功后的临界区内:Lock接口的内存语义保证,首次成功的lock操作已经完成了完整的内存同步,你读到的references值一定是全局最新的,完全符合可见性要求。 - 你担心的「可重入加解锁无需提供内存同步效果」的规则对这个场景没有影响:如果是重入加锁,说明当前线程已经持有该Segment的锁,首次加锁时已经完成了内存同步,当前线程对
references的可见性本来就有保障;而且持有锁期间没有其他线程能进入临界区修改references,你本地缓存的数组引用在整个临界区执行过程中都是稳定有效的。 - 你提到的「未成功加解锁无同步效果」的规则只适用于加锁失败的场景(比如
tryLock返回false),而你的代码是在lock()成功后才进入的try块,完全不受这个规则约束。
改写示例
你可以参考如下改写逻辑,功能和原代码完全等价:
protected final class Segment extends ReentrantLock { private volatile Reference<K, V>[] references; private void restructure(boolean allowResize, @Nullable Reference<K, V> ref) { boolean needsResize; lock(); try { // 仅一次volatile读,缓存到本地变量 Reference<K, V>[] refs = this.references; boolean resizing = false; int restructureSize = refs.length; //... Reference<K, V>[] restructured = resizing ? createReferenceArray(restructureSize) : refs; for (int i = 0; i < refs.length; i++) { ref = refs[i]; //... } if (resizing) { this.references = restructured; // 直接用新的数组对象计算阈值,不需要再读volatile字段 this.resizeThreshold = (int) (restructured.length * getLoadFactor()); } //... } finally { unlock(); } }
内容的提问来源于stack exchange,提问作者Sergey Tsypanov
相关产品推荐
相关产品推荐

