📜 ⬆️ ⬇️

Right singleton in java

I am sure that each of the readers knows what the “Singleton” design pattern is, but not everyone knows how to program it effectively and correctly. This article is an attempt to aggregate existing knowledge on this issue.

In addition, we can consider the article as a continuation of the remarkable study published earlier in Habrahabr.

Unholy Singleton in Java

The author knows two ways to implement a template with normal initialization.

1 Static field

public class Singleton { public static final Singleton INSTANCE = new Singleton(); } 

+ Simple and transparent implementation
+ Thread safety
- Not lazy initialization
')
2 Enum Singleton

According to Joshua Bloch, this is the best way to implement a template [1].

 public enum Singleton { INSTANCE; } 

+ Witty
+ Serialization out of the box
+ Out-of-box thread safety
+ Ability to use EnumSet, EnumMap, etc.
+ Switch support
- Not lazy initialization

Lazy singleton in java

At the time of writing, there are at least three valid implementations of the Singleton pattern with lazy initialization in Java.

1 Synchronized Accessor

 public class Singleton { private static Singleton instance; public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 

+ Lazy initialization
- Low performance (critical section) in the most typical access

2 Double Checked Locking & volatile

 public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { Singleton localInstance = instance; if (localInstance == null) { synchronized (Singleton.class) { localInstance = instance; if (localInstance == null) { instance = localInstance = new Singleton(); } } } return localInstance; } } 

+ Lazy initialization
+ High performance
- Supported only with JDK 1.5 [5]

2.1 Why doesn't it work without volatile?

The problem of the Double Checked Lock idiom lies in the Java memory model, more precisely in the order in which objects are created. It is possible to conditionally present this order by the following stages [2, 3]:

Suppose we create a new student: Student s = new Student (), then

1) local_ptr = malloc (sizeof (Student)) // memory allocation for the object itself;
2) s = local_ptr // initialization of the pointer;
3) Student :: ctor (s); // construction of the object (initialization of fields);

Thus, between the second and third stage, a situation is possible in which another thread can receive and start using (based on the condition that the pointer is not zero) an incompletely constructed object. In fact, this problem was partially solved in JDK 1.5 [5], but the authors of JSR-133 [5] recommend using voloatile for Double Cheated Lock. Moreover, their attitude to such things is easily traced from the comment to the specification:

There exist a number of common but dubious coding idioms, such as the double-checked locking idiom, that is. Semantics are almost always idioms.

Thus, although the problem has been solved, using Double Checked Lock without volatile is extremely dangerous. In some cases, depending on the implementation of the JVM, operating environment, scheduler, etc., this approach may not work. However, with a series of experiments accompanied by viewing the assembler code generated by JIT to the author, such a case could not be solved.

Finally, Double Checked Lock can be used without exception with immutable objects (String, Integer, Float, etc.).

3 On Demand Holder idiom

 public class Singleton { public static class SingletonHolder { public static final Singleton HOLDER_INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.HOLDER_INSTANCE; } } 


+ Lazy initialization
+ High performance
- Cannot be used for non-static class fields

Performance

To compare the performance of the above methods, a micro-benchmark [6] was used, which determines the number of elementary operations (field increment) per second over a Singleton object, from two parallel threads.

Measurements were made on a dual-core machine Intel Core 2 Duo T7300 2GHz, 2Gb ram and Java HotSpot (TM) Client VM (build 17.0-b17). The unit increment count (and hence the object captures) per second is * 100,000.

(more is better)
ClientServer
Synchronized accessor42.686.3
Double Checked Lock & volatile179.8202.4
On Demand Holder181.6202.7


Conclusion: if you choose the right implementation of the template, you can get acceleration (speed up) from 2x to 4x .

Summary

We can single out the following short advice on the use of one approach or another for the implementation of the “Loner” template [1].

1) Use normal (not lazy) initialization wherever possible;
2) For static fields use On Demand Holder idom;
3) For simple fields use Double Chedked Lock & volatile idom;
4) In all other cases, use the Syncronized accessor;

Java Class Library & Singleton

It is noteworthy that the developers of the Java Class Library chose the easiest way to implement the template - Syncronized Accessor. On the one hand, this is a guarantee of compatibility and proper operation. On the other hand, this is the loss of processor time to enter and exit the critical section during each access.

A quick search for grep from source made it clear that there are a lot of such places in JCL.

Maybe the next article will be “What will happen if in the Java Class Library correctly write all singleton classes?” :)

Links
[1] Joshua Bloch, Effective Java Reloaded talk at Google I / O 2008 ( video );
[2] Double-checked locking and the Singleton pattern ;
[3] The "Double-Checked Locking is Broken" Declaration ;
[4] en.wikipedia.org/wiki/Double-checked_locking
[5] JSR-133
[6] How to write microbenchmarks

Source: https://habr.com/ru/post/129494/


All Articles