为什么 JDK 源代码会获取“易失性”实例的“最终”副本

2022-08-31 13:30:20

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

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

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

我的问题是:

“this.segments”声明:

final Segment<K,V>[] segments;

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

作者为什么这样写呢?他们为什么不直接使用 this.segments?有什么原因吗?


答案 1

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

此外,即使所讨论的成员变量不是易失性的,而是最终的,这个习语也与CPU缓存有关,因为从堆栈位置读取比从随机堆位置读取更有利于缓存。本地 var 最终绑定到 CPU 寄存器的可能性也更高。

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


答案 2

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

你可以参考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;
}

他写道:

此代码可能看起来有点复杂。特别是,需要局部变量的结果可能不清楚。此变量的作用是确保字段在已初始化的常见情况下仅读取一次。虽然不是严格必要的,但这可能会提高性能,并且根据适用于低级并发编程的标准,它更优雅。在我的机器上,上面的方法比没有局部变量的明显版本快25%左右。


推荐