Java 延迟加载的单例:双重检查锁定与按需初始化持有者习惯用法
我需要在并发环境中延迟加载资源。加载资源的代码只应执行一次 两者(使用JRE 5+和volatile关键字)和似乎都很适合这份工作 只要看一下代码,按需初始化持有者的习惯用法似乎更干净、更高效(不过,我在这里猜)。尽管如此,我还是要在我的每一个单身汉身上记录下这种模式。至少对我来说,很难理解为什么代码是这样写的 我的问题是:哪种方法更好?为什么? 如果你的答案是没有。您将如何在JavaSE环境中处理此需求 备选方案Java 延迟加载的单例:双重检查锁定与按需初始化持有者习惯用法,java,concurrency,singleton,lazy-loading,design-patterns,Java,Concurrency,Singleton,Lazy Loading,Design Patterns,我需要在并发环境中延迟加载资源。加载资源的代码只应执行一次 两者(使用JRE 5+和volatile关键字)和似乎都很适合这份工作 只要看一下代码,按需初始化持有者的习惯用法似乎更干净、更高效(不过,我在这里猜)。尽管如此,我还是要在我的每一个单身汉身上记录下这种模式。至少对我来说,很难理解为什么代码是这样写的 我的问题是:哪种方法更好?为什么? 如果你的答案是没有。您将如何在JavaSE环境中处理此需求 备选方案 我可以在不将CDI用于整个项目的情况下使用CDI吗?有文章吗?添加另一个可能更干
我可以在不将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-微基准测试并不是坏事。很难得到有意义的、适用于实际用例的结果。有时称为虚拟代理模式。。。该实现很好地说明了“雷霆万钧”问题的解决方案,其中多个调用者请求相同的资源,但您只想获取一次资源。