Correct Singleton in Java
I am sure that each reader knows what the “Singleton” design pattern is, but not everyone knows how to program it efficiently and correctly. This article is an attempt to aggregate existing knowledge on this issue.
In addition, you can consider the article as a continuation of the remarkable study published on Habrahabr earlier.
The author knows two ways to implement a template with normal initialization.
+ Simple and transparent implementation
+ Thread safety
- No lazy initialization
According to Joshua Bloch, this is the best way to implement the template [1].
+ Witty
+ Serialization out of the box
+ Thread safety out of the box
+ Ability to use EnumSet, EnumMap, etc.
+ Switch support
- No lazy initialization
At the time of writing, there are at least three valid implementations of the Singleton template with lazy initialization in Java.
+ Lazy initialization
- Poor performance (critical section) in the most typical access
+ Lazy initialization
+ High performance
- Only supported with JDK 1.5 [5]
The problem of the Double Checked Lock idiom lies in the Java memory model, more precisely in the order in which objects are created. You can conditionally imagine this order in the following steps [2, 3]:
Let us create a new student: Student s = new Student (), then
1) local_ptr = malloc (sizeof (Student)) // allocate memory for the object itself;
2) s = local_ptr // initialization of the pointer;
3) Student :: ctor (s); // object construction (field initialization);
Thus, between the second and third stages, 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. Actually, this problem was partially solved in JDK 1.5 [5], however, the authors of JSR-133 [5] recommend using voloatile for Double Chess Locked. Moreover, their attitude to such things can be easily traced from the commentary on the specification:
Thus, although the problem has been resolved, 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, a series of experiments accompanied by viewing the assembler code generated by JIT by the author failed to raise such a case.
Finally, Double Checked Lock can be used without exception with immutable objects (String, Integer, Float, etc.).
+ Lazy initialization
+ High performance
- Cannot be used for non-static class fields
To compare the performance of the above methods, a micro-benchmark was used [6], which determines the number of elementary operations (field increment) per second over a Singleton object, from two parallel threads.
The measurements were performed on a dual-core Intel Core 2 Duo T7300 2GHz machine, 2Gb ram and Java HotSpot (TM) Client VM (build 17.0-b17). The unit of speed is the number of increments (and hence the object’s captures) per second * 100,000.
(More is better)
Conclusion: if you choose the right implementation of the template, you can get acceleration (speed up) from 2x to 4x .
We can distinguish the following short tips on using this or that approach to implement 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;
It is noteworthy that the developers of the Java Class Library have chosen the easiest way to implement the template - Syncronized Accessor. On the one hand, it is a guarantee of compatibility and proper operation. On the other hand, this is the loss of processor time for entering and exiting the critical section with each call.
A quick grep search by source made it clear that there are a lot of such places in JCL.
Perhaps the next article will be “What happens if all Singleton classes are written correctly in the Java Class Library?” :)
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
In addition, you can consider the article as a continuation of the remarkable study published on Habrahabr earlier.
Lazy 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
- No lazy initialization
2 Enum Singleton
According to Joshua Bloch, this is the best way to implement the template [1].
public enum Singleton {
INSTANCE;
}
+ Witty
+ Serialization out of the box
+ Thread safety out of the box
+ Ability to use EnumSet, EnumMap, etc.
+ Switch support
- No lazy initialization
Lazy Singleton in Java
At the time of writing, there are at least three valid implementations of the Singleton template 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
- Poor 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
- Only supported 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. You can conditionally imagine this order in the following steps [2, 3]:
Let us create a new student: Student s = new Student (), then
1) local_ptr = malloc (sizeof (Student)) // allocate memory for the object itself;
2) s = local_ptr // initialization of the pointer;
3) Student :: ctor (s); // object construction (field initialization);
Thus, between the second and third stages, 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. Actually, this problem was partially solved in JDK 1.5 [5], however, the authors of JSR-133 [5] recommend using voloatile for Double Chess Locked. Moreover, their attitude to such things can be easily traced from the commentary on the specification:
There exist a number of common but dubious coding idioms, such as the double-checked locking idiom, that are proposed to allow threads to communicate without synchronization. Almost all such idioms are invalid under the existing semantics, and are expected to remain invalid under the proposed semantics.
Thus, although the problem has been resolved, 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, a series of experiments accompanied by viewing the assembler code generated by JIT by the author failed to raise such a case.
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 was used [6], which determines the number of elementary operations (field increment) per second over a Singleton object, from two parallel threads.
The measurements were performed on a dual-core Intel Core 2 Duo T7300 2GHz machine, 2Gb ram and Java HotSpot (TM) Client VM (build 17.0-b17). The unit of speed is the number of increments (and hence the object’s captures) per second * 100,000.
(More is better)
Client | Server | |
---|---|---|
Synchronized accessor | 42.6 | 86.3 |
Double Checked Lock & volatile | 179.8 | 202.4 |
On demand holder | 181.6 | 202.7 |
Conclusion: if you choose the right implementation of the template, you can get acceleration (speed up) from 2x to 4x .
Summary
We can distinguish the following short tips on using this or that approach to implement 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 have chosen the easiest way to implement the template - Syncronized Accessor. On the one hand, it is a guarantee of compatibility and proper operation. On the other hand, this is the loss of processor time for entering and exiting the critical section with each call.
A quick grep search by source made it clear that there are a lot of such places in JCL.
Perhaps the next article will be “What happens if all Singleton classes are written correctly in the Java Class Library?” :)
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