简体   繁体   English

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

[英]Lazy-loaded singleton: Double-checked locking vs Initialization on demand holder idiom

I have a requirement to lazy-load resources in a concurrent environment.我需要在并发环境中延迟加载资源。 The code to load the resources should be executed only once.加载资源的代码应该只执行一次。

Both Double-checked locking (using JRE 5+ and the volatile keyword) and Initialization on demand holder idiom seems to fit the job well.双重检查锁定(使用 JRE 5+ 和 volatile 关键字)和按需初始化持有者习语似乎都适合这项工作。

Just by looking at the code, Initialization on demand holder idiom seems cleaner and more efficient (but hey, I'm guessing here).只看代码,Initialization on demand holder idiom 似乎更干净、更高效(但是,嘿,我猜这里)。 Still, I will have to take care and document the pattern at every one of my Singletons.尽管如此,我还是必须注意并记录每个单身人士的模式。 At least to me, It would be hard to understand why code was written like this on the spot...至少对我来说,很难理解为什么当场写成这样的代码......

My question here is: Which approach s is better?我的问题是:哪种方法更好? And why?为什么? If your answer is none.如果你的答案是否定的。 How would you tackle this requirement in a Java SE environment?您将如何在 Java SE 环境中解决此要求?

Alternatives备择方案

Could I use CDI for this without imposing it's use over my entire project?我可以为此使用 CDI 而不强制它在我的整个项目中使用吗? Any articles out there?有文章出来吗?

To add another, perhaps cleaner, option.添加另一个可能更清洁的选项。 I suggest the enum variation:我建议枚举变体:

What is the best approach for using an Enum as a singleton in Java?在 Java 中使用枚举作为 singleton 的最佳方法是什么?

As far as readability I would go with the initialization on demand holder.就可读性而言,我会使用按需初始化的 go 持有人。 The double checked locking, I feel, is a dated and an ugly implementation.我觉得双重检查锁定是一个过时且丑陋的实现。

Technically speaking, by choosing double checked locking you would always incur a volatile read on the field where as you can do normal reads with the initialization on demand holder idiom.从技术上讲,通过选择双重检查锁定,您总是会在字段上产生易失性读取,因为您可以使用按需初始化持有者习语进行正常读取。

Initialisation-on-demand holder only works for a singleton, you can't have per-instance lazily loaded elements.按需初始化持有人仅适用于 singleton,您不能拥有每个实例延迟加载的元素。 Double-checked locking imposes a cognitive burden on everyone who has to look at the class, as it is easy to get wrong in subtle ways.双重检查锁定给必须查看 class 的每个人带来认知负担,因为很容易以微妙的方式出错。 We used to have all sorts of trouble with this until we encapsulated the pattern into utility class in our concurrency library在我们将模式封装到并发库中的实用程序 class 之前,我们曾经遇到过各种各样的麻烦

We have the following options:我们有以下选择:

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

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

Both have identical semantics as far as the usage is concerned.就用法而言,两者具有相同的语义。 The second form makes any references used by the inner supplier available to GC after initialisation.第二种形式使内部供应商使用的任何引用在初始化后都可用于 GC。 The second form also has support for timeouts with TTL/TTI strategies.第二种形式还支持使用 TTL/TTI 策略的超时。

Initialization-on-demand holder is always best practice for implementing singleton pattern.按需初始化持有者始终是实现 singleton 模式的最佳实践。 It exploits the following features of the JVM very well.它很好地利用了 JVM 的以下特性。

  1. Static nested classes are loaded only when called by name. Static 嵌套类仅在按名称调用时加载。
  2. The class loading mechanism is by default concurrency protected. class 加载机制默认受并发保护。 So when a thread initializes a class, the other threads wait for its completion.所以当一个线程初始化一个 class 时,其他线程等待它的完成。

Also, you don't have to use the synchronize keyword, it makes your program 100 times slower.此外,您不必使用 synchronize 关键字,它会使您的程序慢 100 倍。

I suspect that the initialization on demand holder is marginally faster that double-checked locking (using a volatile).我怀疑按需初始化持有人比双重检查锁定(使用易失性)稍微快一些。 The reason is that the former has no synchronization overhead once the instance has been created, but the latter involves reading a volatile which (I think) entails a full memory read.原因是前者在创建实例后没有同步开销,但后者涉及读取 volatile (我认为)需要完整的 memory 读取。

If performance is not a significant concern, then the synchronized getInstance() approach is the simplest.如果性能不是一个重要的问题,那么同步的getInstance()方法是最简单的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM