繁体   English   中英

延迟加载 singleton:双重检查锁定与按需初始化持有人习惯用法

[英]Lazy-loaded singleton: Double-checked locking vs Initialization on demand holder idiom

我需要在并发环境中延迟加载资源。 加载资源的代码应该只执行一次。

双重检查锁定(使用 JRE 5+ 和 volatile 关键字)和按需初始化持有者习语似乎都适合这项工作。

只看代码,Initialization on demand holder idiom 似乎更干净、更高效(但是,嘿,我猜这里)。 尽管如此,我还是必须注意并记录每个单身人士的模式。 至少对我来说,很难理解为什么当场写成这样的代码......

我的问题是:哪种方法更好? 为什么? 如果你的答案是否定的。 您将如何在 Java SE 环境中解决此要求?

备择方案

我可以为此使用 CDI 而不强制它在我的整个项目中使用吗? 有文章出来吗?

添加另一个可能更清洁的选项。 我建议枚举变体:

在 Java 中使用枚举作为 singleton 的最佳方法是什么?

就可读性而言,我会使用按需初始化的 go 持有人。 我觉得双重检查锁定是一个过时且丑陋的实现。

从技术上讲,通过选择双重检查锁定,您总是会在字段上产生易失性读取,因为您可以使用按需初始化持有者习语进行正常读取。

按需初始化持有人仅适用于 singleton,您不能拥有每个实例延迟加载的元素。 双重检查锁定给必须查看 class 的每个人带来认知负担,因为很容易以微妙的方式出错。 在我们将模式封装到并发库中的实用程序 class 之前,我们曾经遇到过各种各样的麻烦

我们有以下选择:

Supplier<ExpensiveThing> t1 = new LazyReference<ExpensiveThing>() {
  protected ExpensiveThing create() {
    … // expensive initialisation
  }
};

Supplier<ExpensiveThing> t2 = Lazy.supplier(new Supplier<ExpensiveThing>() {
  public ExpensiveThing get() {
    … // expensive initialisation
  }
});

就用法而言,两者具有相同的语义。 第二种形式使内部供应商使用的任何引用在初始化后都可用于 GC。 第二种形式还支持使用 TTL/TTI 策略的超时。

按需初始化持有者始终是实现 singleton 模式的最佳实践。 它很好地利用了 JVM 的以下特性。

  1. Static 嵌套类仅在按名称调用时加载。
  2. class 加载机制默认受并发保护。 所以当一个线程初始化一个 class 时,其他线程等待它的完成。

此外,您不必使用 synchronize 关键字,它会使您的程序慢 100 倍。

我怀疑按需初始化持有人比双重检查锁定(使用易失性)稍微快一些。 原因是前者在创建实例后没有同步开销,但后者涉及读取 volatile (我认为)需要完整的 memory 读取。

如果性能不是一个重要的问题,那么同步的getInstance()方法是最简单的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM