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

问题描述

| 我需要在并发环境中延迟加载资源。加载资源的代码应仅执行一次。 经过双重检查的锁定(使用JRE 5+和volatile关键字)和按需持有人初始化习惯似乎都非常适合。 仅通过查看代码,按需持有人的惯用语初始化似乎更干净,更高效(但是,我猜这里)。尽管如此,我仍要小心并记录每一个Singleton中的模式。至少对我来说,很难理解为什么当场写这样的代码... 我的问题是:哪种方法更好?又为什么呢 如果您的答案为否。您将如何在Java SE环境中解决此要求? 备择方案 我可以为此使用CDI而不在整个项目中使用它吗?有文章吗?     

解决方法

        添加另一个也许更清洁的选项。我建议枚举变量: 在Java中将枚举用作单例的最佳方法是什么?     ,        就可读性而言,我将按需进行初始化。我认为,经过双重检查的锁定是过时且丑陋的实现。 从技术上讲,通过选择双重检查锁定,您总是会在该字段上产生易失性读取,因为您可以使用按需初始化持有人惯用语来进行常规读取。     ,        按需初始化持有人仅适用于单例,您不能按实例延迟加载元素。双重检查锁定给必须看课的每个人增加了认知负担,因为很容易以微妙的方式犯错。在将模式封装到并发库中的实用程序类之前,我们曾经为此遇到过各种麻烦 我们有以下选择:
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策略的超时。     ,        按需初始化持有人始终是实现单例模式的最佳实践。它很好地利用了JVM的以下功能。 静态嵌套类仅在按名称调用时才加载。 默认情况下,类加载机制是并发保护的。因此,当一个线程初始化一个类时,其他线程将等待其完成。 另外,您不必使用syncnize关键字,它会使您的程序慢100倍。     ,        我怀疑按需持有人的初始化要比经过双重检查的锁定(使用volatile)快一些。原因是前者一旦创建实例便没有同步开销,但后者涉及读取volatile(我认为)需要读取完整的内存。 如果性能不是很重要的问题,那么同步“ 1”方法是最简单的。     

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...