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

Ant*_*oly 9 java concurrency singleton design-patterns lazy-loading

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

这两个双重检查锁定(使用JRE 5+和volatile关键字)和初始化按需持有者成语似乎适合这个职业.

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

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

备择方案

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

djg*_*djg 7

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

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

  • 如果您正在处理方法调用中的值的延迟加载(和同步),那么您正在规避枚举单例的好处.我不确定你的所有要求,但我建议你们每个人单独的枚举类实现的接口.或者只包含一个包含所有资源的单例(假设您可以在加载时处理"一次性"的性能). (2认同)

Joh*_*int 7

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

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


Jed*_*ith 7

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

我们有以下选择:

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

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

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


Ahm*_*mad 5

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

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

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