延迟加载的单例:双重检查锁定与按需持有者惯用语初始化

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

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

仅仅通过查看代码,按需初始化持有者习惯看起来更干净,更有效(但是,嘿,我在这里猜测)。 不过,我必须小心并记录我的每一个单身人士的模式。 至少对我而言,很难理解为什么代码是这样编写的……

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

备择方案

我可以使用CDI而不强加它在整个项目中的使用吗? 那里有文章吗?

添加另一个,也许是更清洁的选项。 我建议枚举变化:

在Java中使用Enum作为单例的最佳方法是什么?

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

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

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

我们有以下选择:

 Supplier t1 = new LazyReference() { protected ExpensiveThing create() { … // expensive initialisation } }; Supplier t2 = Lazy.supplier(new Supplier() { public ExpensiveThing get() { … // expensive initialisation } }); 

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

初始化按需持有者始终是实施单例模式的最佳实践。 它很好地利用了JVM的以下function。

  1. 只有在按名称调用时才会加载静态嵌套类。
  2. 默认情况下,类加载机制受并发保护。 因此,当一个线程初始化一个类时,其他线程会等待它的完成。

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

我怀疑按需检查锁定(使用易失性)时,按需初始化的持续时间略快。 原因是前者在创建实例后没有同步开销,但后者涉及读取一个volatile(我认为)需要完整的内存读取。

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