为什么 JDK 的源代码采用了’易失性’实例的’最终’副本

我阅读了 JDK 关于 ConcurrentHashMap 的源代码。

但是下面的代码让我很困惑:

public boolean isEmpty() {
final Segment<K,V>[] segments = this.segments;
...
}

我的问题是:

声明为:

final Segment<K,V>[] segments;

因此,在这里,在方法的开头,声明了一个相同的类型引用,指向相同的内存。

为什么作者要这样写? 为什么他们不直接用这个段落? 有什么原因吗?

2512 次浏览

这是典型的涉及 volatile变量的无锁代码习惯用法。在第一行,你读一次 volatile,然后使用它。与此同时,另一个线程可以更新 volatile,但您只对最初读取的值感兴趣。

此外,即使所讨论的成员变量不是易失性的,而是最终的,这个习惯用法也与 CPU 缓存有关,因为从堆位置读取数据比从随机堆位置读取数据对缓存更友好。本地 var 最终绑定到 CPU 寄存器的可能性也更大。

对于后一种情况,实际上存在一些争议,因为 JIT 编译器通常会处理这些问题,但 Doug Lea 是坚持一般原则的人之一。

我想这是出于性能考虑,因此我们只需要检索一次字段值。

您可以从 Joshua Bloch 的有效 java 中引用一个单例习惯用法

他的独角兽在这里:

private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) {
synchronized(this) {
result = field;
if (result == null)
field = result = computeFieldValue();
}
}
return result;
}

他写道:

这段代码可能看起来有点复杂 局部变量 result 可能不清楚。这个变量的作用是 确保该字段只被读取一次 虽然严格来说没有必要,但是这可能会改进 性能更优雅,按标准应用于低层次 在我的机器上,上面的方法大约是25 比没有局部变量 .

的明显版本快百分比

它可以减少字节码大小-访问本地变量比访问实例变量短。

运行时优化开销也可以减少。

但这些都不重要。更多的是代码风格。如果您对实例变量感到满意,请务必。Doug Lea 可能更擅长处理局部变量。