在Joshua Bloch有效的Java示例中,为什么双重检查锁定速度快25%

在Joshua Bloch有效的Java示例中,为什么双重检查锁定速度快25%,java,multithreading,jakarta-ee,volatile,Java,Multithreading,Jakarta Ee,Volatile,下面是来自有效Java第二版的代码片段。在这里,作者声称下面的代码比不使用结果变量的代码快25%。 根据这本书,“这个变量的作用是确保字段在已经初始化的常见情况下只读取一次。”。 我无法理解,如果不使用局部变量结果,为什么在值初始化为of之后,该代码会很快。在这两种情况下,无论是否使用局部变量结果,初始化后都只有一个volatile read // Double-check idiom for lazy initialization of instance fields private vol

下面是来自有效Java第二版的代码片段。在这里,作者声称下面的代码比不使用结果变量的代码快25%。 根据这本书,“这个变量的作用是确保字段在已经初始化的常见情况下只读取一次。”。 我无法理解,如果不使用局部变量结果,为什么在值初始化为of之后,该代码会很快。在这两种情况下,无论是否使用局部变量结果,初始化后都只有一个volatile read

// Double-check idiom for lazy initialization of instance fields 
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;
}

初始化
字段后,代码为:

if (field == null) {...}
return field;
或:

在第一种情况下,您读取volatile变量两次,而在第二种情况下,您只读取一次。虽然volatile读取速度非常快,但它们可能比从局部变量读取速度慢一点(我不知道是否为25%)

注:

  • 易失性读取与最新处理器(至少x86)/JVM上的正常读取一样便宜,即没有区别
  • 但是,编译器可以更好地优化代码,而不需要使用volatile,因此您可以从更好的编译代码中获得效率
  • 几纳秒的25%仍然不算多
  • 这是一种标准的习惯用法,您可以在java.util.concurrent包的许多类中找到它——例如,请参见(其中有很多)

    • 在不使用局部变量的情况下,在大多数调用中,我们可以有效地

      if(field!=null) // true
          return field;
      
      所以有两个易失性读取,这比一个易失性读取慢

      实际上,JVM可以将两个易失性读取合并为一个易失性读取,并且仍然符合JMM。但我们希望JVM在每次被告知时都能执行一次真诚的易失性读取,而不是像个聪明人一样,尝试优化任何易失性读取。考虑这个代码

      volatile boolean ready;
      
      do{}while(!ready); // busy wait
      

      我们希望JVM能够反复加载变量。

      Uhm,这是第一版吗?双重检查锁定已经被劝阻好几年了,原因是:@fge:不太可能。从Java5开始,它实际上是一个OK模式:@LukasEder它基本上是不受欢迎的,因为Java6+基本上呈现了它obsolete@Lenymm您提供的链接没有谈到局部变量Result.ok,所以它的意思是“返回字段”也构成了一个volatile read(提供的字段是volatile)@veritas是的,确实如此-如果您检查字节码,您将看到它在返回之前已加载。+1:很高兴看到一个独立于双重检查锁定的示例有关双重检查锁定(DCL)和安全出版物的有趣阅读可在此处找到:。根据其测试,使用此局部变量在x86上没有性能提升。在ARM上,使用局部变量比普通DCL的性能提高约15%。
      volatile boolean ready;
      
      do{}while(!ready); // busy wait