为什么JDK源代码要获取volatile实例的最终副本


74

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

但是以下代码使我感到困惑:

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

我的问题是:

声明“ this.segments”:

final Segment<K,V>[] segments;

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

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

Answers:


94

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

同样,即使所讨论的成员变量不是volatile而是final时,该习惯用法也与CPU缓存有关,因为从堆栈位置进行读取比从随机堆位置进行读取更易于缓存。本地var最终绑定到CPU寄存器的机会也更高。

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


因此,如果有人更改this.segments内容,您将不会看到该更改segments
brimborium 2012年

当然,您会看到它。但是,如果有人为分配了其他内容segments,显然您将与之隔离。
Marko Topolnik

3
是的,可以。误会了你的答案...;)
brimborium 2012年

作为段(实例变量)被声明为最终的,它不能改变吗?我记得在重复检查的习惯用法中添加superfluos loval变量可以加快某些vm中的执行速度。也许这是类似的情况。(@Marko已经添加了更详细的解释,谢谢!)
Pyranja

6
查看变量读取与其他所有变量隔离,确实可以提高速度,但是即使是最坏情况下的读取操作性能也通常会被代码的所有其他方面淹没。这就是为什么在现实生活中的应用程序中您几乎不可能注意到差异的原因。
Marko Topolnik

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;
}

他写道:

此代码可能看起来有些混乱。特别是,对局部变量结果的需求可能不清楚。该变量的作用是确保在已初始化字段的常见情况下,该字段仅被读取一次。尽管不是严格必需的,但是这可以提高性能,并且通过应用于低级并发编程的标准可以更加优雅。在我的机器上,上述方法比不带局部变量的明显版本要快25%


4

它可能会减小字节代码的大小-访问本地变量的字节代码比访问实例变量的时间短。

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

但是这些都不重要。更多关于代码样式。如果您对实例变量感到满意,则一定可以。Doug Lea的可能感觉更舒适的交易与局部变量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.