Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/347.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/vb.net/15.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 延迟加载的单例:双重检查锁定与按需初始化持有者习惯用法_Java_Concurrency_Singleton_Lazy Loading_Design Patterns - Fatal编程技术网

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

Java 延迟加载的单例:双重检查锁定与按需初始化持有者习惯用法,java,concurrency,singleton,lazy-loading,design-patterns,Java,Concurrency,Singleton,Lazy Loading,Design Patterns,我需要在并发环境中延迟加载资源。加载资源的代码只应执行一次 两者(使用JRE 5+和volatile关键字)和似乎都很适合这份工作 只要看一下代码,按需初始化持有者的习惯用法似乎更干净、更高效(不过,我在这里猜)。尽管如此,我还是要在我的每一个单身汉身上记录下这种模式。至少对我来说,很难理解为什么代码是这样写的 我的问题是:哪种方法更好?为什么? 如果你的答案是没有。您将如何在JavaSE环境中处理此需求 备选方案 我可以在不将CDI用于整个项目的情况下使用CDI吗?有文章吗?添加另一个可能更干

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

两者(使用JRE 5+和volatile关键字)和似乎都很适合这份工作

只要看一下代码,按需初始化持有者的习惯用法似乎更干净、更高效(不过,我在这里猜)。尽管如此,我还是要在我的每一个单身汉身上记录下这种模式。至少对我来说,很难理解为什么代码是这样写的

我的问题是:哪种方法更好?为什么? 如果你的答案是没有。您将如何在JavaSE环境中处理此需求

备选方案


我可以在不将CDI用于整个项目的情况下使用CDI吗?有文章吗?

添加另一个可能更干净的选项。我建议使用枚举变量:


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


从技术上讲,通过选择双重检查锁定,您将始终在字段上引发易失性读取,而您可以使用initialization on demand holder习惯用法进行正常读取

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


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

按需初始化持有者仅适用于单例,您不能让每个实例延迟加载元素。双重检查锁定给每个必须查看类的人带来了认知负担,因为它很容易以微妙的方式出错。在将模式封装到中的实用程序类之前,我们在这方面遇到过各种各样的问题

我们有以下选择:

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

Supplier<ExpensiveThing> t2 = Lazy.supplier(new Supplier<ExpensiveThing>() {
  public ExpensiveThing get() {
    … // expensive initialisation
  }
});
Supplier t1=new LazyReference(){
受保护的费用创建(){
…//昂贵的初始化
}
};
供应商t2=惰性供应商(新供应商(){
公共开支{
…//昂贵的初始化
}
});

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

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

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

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

    Nice。但我不确定我是否掌握了它的工作原理。比如说,
    Elvis.getAge()
    需要一些非常密集的操作,我希望这些操作能够尽可能地延迟,并且只执行一次。我应该把我的装载代码放在哪里?在枚举构造函数中?@djg对我来说已经足够好了。但是有没有办法进一步推迟计算呢。例如,假设我想独立加载几个不同的资源。使用此模式,我必须编写不同的枚举。有没有办法只使用一个枚举来实现这一点?让我们用
    INSTANCE1.getAge()
    INSTANCE2.getAge()
    来说
    Elvis
    。我可以让它们以线程安全的方式独立加载吗?对。我在考虑单独的枚举/单例。如果您有多个“实例”,那么将为每个实例调用一次构造函数。据我所知,这是不可取的。@djg。我想我表达得不好。我的意思是。如果我想要
    INSTANCE1.getAge()
    延迟加载一个值,而
    INSTANCE2.getAge()
    延迟加载另一个值,该怎么办?在这两种情况下,它们应该只加载一次它各自的值(并且单独加载)。有可能实现这种行为吗?(多个枚举很好,我只是想知道它是否可以改进)。如果您处理方法调用中值的延迟加载(和同步),那么您就是在规避枚举单例的好处。我不确定您的所有需求,但我建议您每个单独的单例枚举类都要实现一个接口。或者只是一个包含所有资源的单例(假定您可以在加载时处理“一次完成”的性能)。那么,就速度而言:按需初始化持有者>双重检查锁定>同步方法,对吗?我也是这么想的。“也许是时候进行一些邪恶的微观基准测试了。”安东尼,没错。基本上,需求持有者在没有不稳定负载的情况下实现了您希望DCL做的事情。@Anthony-微基准测试并不是坏事。很难得到有意义的、适用于实际用例的结果。有时称为虚拟代理模式。。。该实现很好地说明了“雷霆万钧”问题的解决方案,其中多个调用者请求相同的资源,但您只想获取一次资源。