为什么JDK源代码采用`volatile`实例的`final`副本

HUA*_* Di 74 java

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

但以下代码让我感到困惑:

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

我的问题是:

声明"this.segments":

final Segment<K,V>[] segments;
Run Code Online (Sandbox Code Playgroud)

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

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

Mar*_*nik 94

这是涉及volatile变量的无锁代码的典型惯用语.在第一行,您阅读volatile一次,然后使用它.在此期间,另一个线程可以更新volatile,但您只对最初阅读的值感兴趣.

此外,即使有问题的成员变量不是易失性但最终,这个习惯用法与CPU缓存有关,因为从堆栈位置读取比从随机堆位置读取更加缓存友好.本地var最终绑定到CPU寄存器的可能性也更高.

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

  • 查看与其他所有内容隔离读取的变量,加速确实很重要,但即使是最坏情况的读取操作性能通常也会被代码的所有其他方面淹没.这就是为什么在现实生活中,你不太可能注意到这种差异. (6认同)
  • 啊,是的,当然.误解了你的答案......;) (3认同)

lar*_*.li 19

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

你可以参考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;
}
Run Code Online (Sandbox Code Playgroud)

他写道:

此代码可能看起来有点复杂.特别是,对局部变量结果的需求可能不清楚.这个变量的作用是确保该字段在已经初始化的常见情况下只读一次.虽然不是绝对必要的,这样可以提高性能,是适用于低层次的并行编程标准更优雅.在我的机器上,上面的方法比没有局部变量的明显版本快25%.