为什么JDK源代码需要`volatile`实例的`final`副本
我读了关于ConcurrentHashMap的JDK源代码。
但是下面的代码让我困惑:
public boolean isEmpty() { final Segment<K,V>[] segments = this.segments; ... }
我的问题是:
“this.segments”被声明为:
final Segment<K,V>[] segments;
所以,在这里,在方法的开始,声明一个相同的types引用,指向相同的内存。
为什么作者这样写呢? 为什么他们不直接使用this.segments? 有什么理由吗?
这是一个典型的涉及volatile
variables的无锁代码。 在第一行你读了一次volatile
,然后使用它。 在此期间另一个线程可以更新volatile
,但是您只对最初读取的值感兴趣。
另外,即使所讨论的成员variables不是易失性的,也不是最终的,这个习惯用法与CPUcaching相关,因为从堆栈位置读取比从随机堆位置读取更容易caching。 本地variables最终会绑定到一个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; }
他写道:
这段代码可能显得有些复杂。 特别是对局部variables结果的需求可能不清楚。 这个variables的作用是确保该字段在已经初始化的常见情况下只能读取一次。 虽然不是绝对必要的,但是这可以提高性能,并且通过适用于低级并发编程的标准更加优雅。 在我的机器上,上面的方法比没有局部variables的明显版本快大约25% 。
它可以减less字节码的大小 – 访问本地variables的字节码比访问实例variables更短。
运行时优化开销也可能会降低。
但是这些都不重要。 这更多的是代码风格。 如果你对实例variables感到满意的话,一定要这样做。 道格·李(Doug Lea)可能会感觉更适合处理局部variables。