【问题标题】:Can I avoid the volatile read in double checked locking once initialized like this?一旦像这样初始化,我可以避免双重检查锁定中的易失性读取吗?
【发布时间】:2012-01-02 11:40:30
【问题描述】:

通过使用仅在实例为 null 时才读取的 volatile 布尔值,我是否可以像这样初始化实例后避免默认/频繁的 volatile 读取?还没有看到有人推荐这样的双重检查锁定,但似乎在完全初始化后避免了易失性读取......

public class Singleton {

   private static volatile boolean initialized = false;
   private static Object lock = new Object();
   private static Singleton instance;

      public static Singleton getInstance(){
           if(instance != null) return instance;
           if(!initialized){
                synchronized(lock){
                   if(!initialized){
                       instance = new Singleton();
                       initialized = true;
                   }
                }
           }
           return instance;

       }

}

【问题讨论】:

    标签: java concurrency


    【解决方案1】:

    不,你不能。如果线程 A 已经到达同步块并且正在执行

    instance = new Singleton();
    

    行,进入你的函数的线程 B 可以看到 instance 在线程 A 完成构造对象之前初始化。因此,您冒着让线程 B 尝试处理部分构造的对象的风险。

    有关此模式的变体和解释,请参阅 DLCP 文章。

    【讨论】:

      【解决方案2】:

      您的优化尝试不仅为时过早,而且毫无结果,引用Are volatile variable 'reads' as fast as normal reads?

      在 x86 上,没有额外的开销与易失性读取相关。

      【讨论】:

      • 几乎所有的硬件读取如果命中缓存就会很便宜,否则不会。
      【解决方案3】:

      如果您想要惰性实例化但又关心性能,请使用“内部类”方法 - 它更简洁(并且效率也更高一点)。类似的东西:

      public class Singleton {
      
          private static class SingletonHolder {
              private static final Singleton instance = new Singleton();
          }
      
          public static Singleton getInstance(){
              return SingletonHolder.instance;
          }
      
      }
      

      诀窍是实例是在内部类的初始化期间创建的——这将在第一次引用类时发生,即第一次调用getInstance()。 顺便说一句 - 避免使用这种“静态”单例,除非确实有充分的理由使用它们。

      【讨论】:

      • 内部类实际上更糟,因为 volatile 读取实际上是免费的。内部类只会增加启动加载时间,并在 perm gen + 额外的样板代码中创建无用的东西。
      • @bestss - 除非只有几个类而不是 95% 的代码是单例 perm gen 不是问题;为什么在使用典型同步块时启动时间应该更长?另外 - 你认为双重锁定是较少的样板代码吗?
      • 我倾向于有类似 LazyReference 的东西,它在 c-tor 中采用 Callable ,就是这样,你不能用一个显式的类来提供一个静态的 final 字段.引导时间较长 b/c 必须从磁盘加载类(解压缩、验证等)。这是对类加载器的额外调用,这往往会导致查找父系统和引导类加载器等。
      • 当然,你可以说用 Callable 简化代码会增加另一个类:D
      • @bestsss - 啊,很好;将“单例”方面与类的构造隔离开来。我还尝试了一些基准测试 - getInstance() 使用“内部类”方法比使用“双重锁定”方法花费的时间减少了约 50%,但在绝对数字中它只是几纳秒。所以,是的,LazyReference 看起来很有吸引力。
      【解决方案4】:

      在大多数硬件上,volatile 读取(几乎)是免费的,但是 volatile 写入很昂贵,但是……那何必呢?

      简短的回答是否定的。 instance = new Singleton()initialized = true 之间的弱内存模型,其中存储可以重新排序,读者可能会看到未初始化的 Signleton。 在 x86 和 sparc TSO 上没有 store-store 重新排序,但在 ARM 和 IA64 上确实会发生。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-27
        • 2020-12-07
        • 1970-01-01
        相关资源
        最近更新 更多