Singleton Pattern: A Deep Dive
This breakdown of the oft-criticized singleton pattern describes some proper use cases for it as well as various means of implementing it.
Join the DZone community and get the full member experience.
Join For FreeThe singleton design pattern is basically used to control the creation of a number of objects, hence belonging to the Creation pattern family.
The earlier trend was to create a max of one object, but in some situations, we need a fixed number of objects; and this pattern is right there to help us. Generally, we mark the constructor as private to ensure that the outside world cannot create objects at all and provides one static method that simply returns the object. It creates an object only when there is no creation beforehand.
With time, people realized several problems with this vanilla implementation of singletons, and it was improved to address those problems. Note that singletons aren't right or wrong; as long as they fit into your problem domain.
In this talk, we will look into various implementations of singletons.
Let us look at the class diagram:
Singleton Uses
There are several places where it is advisable to use the singleton pattern. For example: logging, caching, load balancing, configuration, communication (IO or remote to avoid poor performance) and DB connection-pooling. One example of a singleton in the Java API is the Runtime class.
Singleton Implementations
Here's a vanilla way of implementing a singleton using an eager loading mechanism, which is thread-safe as well.
public class MySingleton {
private static final MySingleton mySingleton = new MySingleton();
private MySingleton(){}
public static MySingleton getInstance(){
return mySingleton;
}
}
On the other hand, here's an example of implementing a singleton using a lazy loading mechanism. In the case of a multi-threaded application, it would be a bad approach.
class MySingleton {
private static MySingleton mySingleton;
private MySingleton(){}
public static MySingleton getInstance(){
if(null == mySingleton) {
mySingleton = new MySingleton();
}
return mySingleton;
}
}
A multi-threaded approach can avoid the race condition to ensure it won't violate the philosophy of a singleton. But in the example below, making the whole method 'synchronized' is not a good approach, as we need to put the lock on the object creation statement only.
class MySingleton {
private static MySingleton mySingleton;
private MySingleton(){}
public synchronized static MySingleton getInstance(){
if(null == mySingleton) {
mySingleton = new MySingleton();
}
return mySingleton;
}
}
The below multi-threaded way of implementation can avoid the race condition to ensure it won't violate the philosophy of singleton and with the help of double-checked locking using object-level lock will achieve the same. This implementation guarantees thread safe; but the extra object is required to just keep a lock which is not a good practice. Another downside is that someone can take the advantage of using class-level lock as your lock is on a different object.
class MySingleton {
private static MySingleton mySingleton;
private static final Object lock = new Object();
private MySingleton(){}
public static MySingleton getInstance(){
if(null == mySingleton) {
synchronized(lock) {
if(null == mySingleton) {
mySingleton = new MySingleton();
}
}
}
return mySingleton;
}
}
Another multi-threaded-based implementation (to avoid race conditions) can be achieved with the help of double-checked locking using a class-level lock. Here, marking the MySingleton object as volatile will ensure that changes made by one thread should be visible in another. This implementation guarantees thread safety.
class MySingleton {
private volatile static MySingleton mySingleton;
private MySingleton() {}
public static MySingleton getInstance() {
if (null == mySingleton) {
synchronized(MySingleton.class) {
if (null == mySingleton) {
mySingleton = new MySingleton();
}
}
}
return mySingleton;
}
}
This means of implementation provides an intelligent constructor that will stop the singleton contract violation using reflection.
class MySingleton {
private volatile static MySingleton mySingleton;
//Reflection can't hack to create more than one object.
private MySingleton() throws Exception {
if (null == mySingleton) {
mySingleton = new MySingleton();
} else {
throw new Exception("It's a singleton class; don't expect more object to get produced");
}
}
public static MySingleton getInstance() throws Exception {
if (null == mySingleton) {
synchronized(MySingleton.class) {
if (null == mySingleton) {
mySingleton = new MySingleton();
}
}
}
return mySingleton;
}
}
Here's a very popular implementation using a static class, which brings the powers of lazy loading and thread safety.
public class MySingleton {
private MySingleton() {}
private static class SingletonUisngInner {
private static MySingleton mySingleton = new MySingleton();
}
public static MySingleton getInstance() {
return SingletonUisngInner.mySingleton;
}
}
In some circumstances, if your singleton class is inheriting Cloneable interface properties, then your singleton class needs extra care to prevent the singleton design contract. Your singleton class should override the clone method and explicitly throws the CloneNotSupportedException.
class ClonedClass implements Cloneable {
//Some logic
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class MySingleton extends ClonedClass {
private MySingleton() {}
private static class SingletonUisngInner {
private static MySingleton mySingleton = new MySingleton();
}
public static OneMore getInstance() {
return singletonUisngInner.mySingleton;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
Another, and our final, very popular and smart way of implementing singletons is using enum, which takes care of all the issues we've so far.
public enum EnumSingleton{
INSTANCE;
}
Sometimes, people talk about singletons across multiple JVMs, so let's touch on that. Singleton means only one object and we know very well that the object lifecycle is managed by the JVM, so one shared object across multiple JVMs is not possible.
But if you need to, you can probably create the object in one JVM and distribute it as a serialized object that could be used by other JVMs (but keep in mind that you are deserializing it, so keep in mind that anything static or marked as transient will not be achieved, and somewhere, the singleton contract is breaking away). You can also try using RMI server objects as singletons to fit your needs.
Happy learning!
Opinions expressed by DZone contributors are their own.
Comments