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

Jim*_*Jim 5 java concurrency multithreading 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;  
}    
Run Code Online (Sandbox Code Playgroud)

它说使用result似乎不需要但实际上确保field只在已经初始化的常见情况下只读取一次.

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

Pét*_*rök 5

解释在下一页(我强调):

这个变量的作用是确保该字段在已经初始化的常见情况下只读一次.虽然不是绝对必要,但这可以提高性能,并且通过应用于低级并发编程的标准更加优雅.在我的机器上,上面的方法比没有局部变量的明显版本快25%.

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

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

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

  • 这并没有真正回答这个问题,OP似乎已经看到了这个解释. (2认同)

Mat*_*der 4

我猜想,该示例中的想法是结果/字段将在下面多次使用。访问result更便宜(它不是易失性的)。

否则,在执行返回时,您将进行第二次易失性读取。

如果您需要这样做,请使用按需初始化持有者模式。 http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

在评论中添加我的一些澄清到答案本身,以便......清晰:

简短版本:局部变量只能位于 cpu(其中之一)的寄存器中(如果有多个,则位于其中一个 cpu 核心)。这已经是最快的了。必须检查易失性变量是否存在其他内核/缓存/CPU/内存中的更改,但详细信息可能非常特定于硬件(缓存行、内存屏障等)。而且还针对 jvm(例如,热点服务器编译器可能会提升非易失性变量),并且它还对重新排序指令施加了限制,以实现可能的性能提升