简体   繁体   中英

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.

Just by looking at the code, Initialization on demand holder idiom seems cleaner and more efficient (but hey, I'm guessing here). 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?

Alternatives

Could I use CDI for this without imposing it's use over my entire project? 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?

As far as readability I would go with the initialization on demand holder. 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. 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. We used to have all sorts of trouble with this until we encapsulated the pattern into utility class in our concurrency library

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. The second form also has support for timeouts with TTL/TTI strategies.

Initialization-on-demand holder is always best practice for implementing singleton pattern. It exploits the following features of the JVM very well.

  1. Static nested classes are loaded only when called by name.
  2. The class loading mechanism is by default concurrency protected. So when a thread initializes a class, the other threads wait for its completion.

Also, you don't have to use the synchronize keyword, it makes your program 100 times slower.

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.

If performance is not a significant concern, then the synchronized getInstance() approach is the simplest.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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