![](/img/trans.png)
[英]Singleton with Enum vs Singleton with double-checked locking
[英]Lazy-loaded singleton: Double-checked locking vs Initialization on demand holder idiom
我需要在并发环境中延迟加载资源。 加载资源的代码应该只执行一次。
双重检查锁定(使用 JRE 5+ 和 volatile 关键字)和按需初始化持有者习语似乎都适合这项工作。
只看代码,Initialization on demand holder idiom 似乎更干净、更高效(但是,嘿,我猜这里)。 尽管如此,我还是必须注意并记录每个单身人士的模式。 至少对我来说,很难理解为什么当场写成这样的代码......
我的问题是:哪种方法更好? 为什么? 如果你的答案是否定的。 您将如何在 Java SE 环境中解决此要求?
备择方案
我可以为此使用 CDI 而不强制它在我的整个项目中使用吗? 有文章出来吗?
添加另一个可能更清洁的选项。 我建议枚举变体:
就可读性而言,我会使用按需初始化的 go 持有人。 我觉得双重检查锁定是一个过时且丑陋的实现。
从技术上讲,通过选择双重检查锁定,您总是会在字段上产生易失性读取,因为您可以使用按需初始化持有者习语进行正常读取。
按需初始化持有人仅适用于 singleton,您不能拥有每个实例延迟加载的元素。 双重检查锁定给必须查看 class 的每个人带来认知负担,因为很容易以微妙的方式出错。 在我们将模式封装到并发库中的实用程序 class 之前,我们曾经遇到过各种各样的麻烦
我们有以下选择:
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 策略的超时。
按需初始化持有者始终是实现 singleton 模式的最佳实践。 它很好地利用了 JVM 的以下特性。
此外,您不必使用 synchronize 关键字,它会使您的程序慢 100 倍。
我怀疑按需初始化持有人比双重检查锁定(使用易失性)稍微快一些。 原因是前者在创建实例后没有同步开销,但后者涉及读取 volatile (我认为)需要完整的 memory 读取。
如果性能不是一个重要的问题,那么同步的getInstance()
方法是最简单的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.