为什么在执行双重检查锁定时将volatile字段复制到局部变量

时间:2012-05-09 08:53:37

标签: java multithreading concurrency synchronization effective-java

我正在阅读Effective Java的双重检查锁定。代码执行以下操作:

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似乎不需要,但实际上确保field仅在已初始化的常见情况下只读取一次。

但我不明白这一点。直接做if(field == null)有什么区别?我不明白为什么if (result == null)是不同的,更不用说如上所述了。

2 个答案:

答案 0 :(得分:5)

解释见下页(我强调):

  

此变量的作用是确保该字段为   在已经初始化的常见情况下只读一次。虽然没有   严格必要,这可以提高性能,并且标准更优雅   适用于低级并发编程。在我的机器上,方法   上面比没有局部变量的明显版本快25%左右。

供参考,报价来自p。第71项中的284:在Effective Java 2nd Edition中明智地使用延迟初始化

更新:读取本地变量与变量之间的区别在于前者可以更好地进行优化。易失性变量不能存储在寄存器或高速缓存中,也不能对它们的存储器操作进行重新排序。此外,读取volatile变量可能会触发不同线程之间的内存同步。

有关详细信息,请参阅实践中的Java并发,第3.1.4节:易失性变量

答案 1 :(得分:4)

在这个例子中的想法是结果/字段将被进一步使用,我想。访问result更便宜(它不易变)。

否则,在返回时会有第二次易失性读取。

如果需要,请使用initializaton on demand holder模式。 http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

在评论中添加我的一些澄清答案本身......明确:

短版本:本地变量只能位于(一个)cpu中的寄存器中(如果是多个等,则位于其中一个cpu的内核中)。这和它一样快。必须检查volatile变量是否有其他core / caches / cpus / memory的更改,但细节可能非常特定于硬件(缓存行,内存屏障等)。但是jvm也是特定的(例如,热点服务器编译器可能会提升非易失性变量)并且它对重新排序指令施加限制以获得可能的性能增益