Java 为什么在执行双重检查锁定时要将volatile字段复制到局部变量
我正在阅读Java 为什么在执行双重检查锁定时要将volatile字段复制到局部变量,java,multithreading,concurrency,synchronization,effective-java,Java,Multithreading,Concurrency,Synchronization,Effective Java,我正在阅读effectivejava中有关双重检查锁定的内容。该代码执行以下操作: private volatile FieldType field; FieldType getField() { FieldType result = field; if (result == null) { // First check (no locking) synchronized(this) { result = field;
effectivejava
中有关双重检查锁定的内容。该代码执行以下操作:
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
它说使用result
似乎没有必要,但实际上可以确保字段在已初始化的常见情况下只读取一次
但我不明白这一点。如果(字段==null)
直接执行有什么区别?我不明白为什么如果(result==null)
是不同的,更不用说更好了 解释在下一页(由我强调):
此变量的作用是确保字段
在已初始化的常见情况下,只读一次。而不是
这是绝对必要的,这可能会提高性能,并且按照标准更加优雅
应用于低级并发编程。在我的机器上,方法
上述方法比没有局部变量的明显版本快25%
作为参考,该报价来自p。第71项284:在有效的Java第二版中明智地使用惰性初始化
更新:读取局部变量和可变变量之间的区别在于前者可能会得到更好的优化。易失性变量不能存储在寄存器或缓存中,也不能对其上的内存操作重新排序。此外,读取易失性变量可能会触发不同线程之间的内存同步
有关更多详细信息,请参见Java并发性实践,第3.1.4节:Volatile变量。该示例中的思想是,我猜结果/字段将被多次使用。访问结果
更便宜(不易波动)
否则,在执行返回操作时,将有第二个volatile read
如果需要这样做,请改用按需初始化持有者模式。
在回答本身的评论中添加我的一些澄清。。。清晰度:
简短版本:局部变量只能位于cpu(其中一个)的寄存器中(如果有多个等,则位于cpu的一个内核中)。这是最快的。必须检查易失性变量在其他内核/缓存/CPU/内存中的变化,但详细信息可能是特定于硬件的(缓存线、内存屏障等)。但也是jvm特有的(例如,hotspot服务器编译器可能会提升非易失性变量),并且它对指令的重新排序施加了限制,以获得可能的性能提升该方法是否进一步使用了result
?我们希望看到“etc”。。。有效Java的页面参考是什么?我没有带我的副本,但其他人可能会带。看,是的,问题中的两个链接@assylias非常好,我仍然把它们放在书签里。布莱恩·戈茨很擅长解释事情。强烈推荐,即使是在80年前(2004年)写的,今天仍然非常相关。这并不能真正回答问题,OP似乎已经看到了这种解释。@PeterTorok:这正是我要问的。为什么这会提高性能?@assylias,嗯,这一点(对我来说)并不明显。无论如何,我已经对我的答案添加了一些解释。@PéterTörök您还可以提到,每次读取volatile
就像一个enter synchronized block操作,因为它必须将任何线程本地缓存与主内存同步。这可能是最贵的部分。是的,很抱歉。错过了最后一句的要点。您可能想提及volatile
在Java中的含义:@Pulsar:因此访问volatile
会更昂贵?@Jim-是的。阅读我贴在上面的链接。虽然这是一个相当复杂的主题,我的意思是细节。但是一个好的开始是java内存模型,否则在执行返回时,您将有第二次volatile读取。