Ankit Sinhal
How to make the perfect Singleton?
Translator: sunluyao
Design patterns are very popular among software developers. Design patterns are good solutions to common software problems. The singleton pattern is one of the creative design patterns in Java.
What is the purpose of the singleton pattern?
The purpose of a singleton class is to control object creation by limiting the number of objects to one and only one. The singleton pattern allows only one entry to create class instances.
Because there is only one instance of a singleton class, any instance of a singleton class will produce only one class, just like a static field. Singletons are useful when you need to control resources, such as database connections or using sockets.
This may seem like a simple design pattern, but when we actually implement it, it causes a lot of implementation problems. The implementation of the singleton pattern has always been controversial among developers. Now, we will discuss how to create a singleton class to accomplish the following purposes:
Restrict class instances and ensure that only one class instance exists in the JVM.
Let’s create a singleton class in Java and test it in different situations.
Create a singleton class
To implement a singleton class, the simplest way is to make the constructor private. There are two methods of initialization.
The hungry type
Hanhanian initialization, where instances of a singleton class are created at class load, is the easiest way to create a singleton class.
By making the constructor private, no other classes are allowed to create instances of the singleton class. Instead, create a static method (usually named getInstance) to provide a unique entry point for creating an instance of the class.
public class SingletonClass { private static volatile SingletonClass sSoleInstance = new SingletonClass(); //private constructor. private SingletonClass(){} public static SingletonClass getInstance() { return sSoleInstance; }}Copy the code
One drawback of this approach is that instances are created even when the program is not using it. This can be quite a problem when you create database connections or sockets, causing memory leaks. The solution is to create instances when needed, which we call lazy initialization.
LanHanShi
In contrast, you initialize the class instance in the getInstance() method. Method determines whether an instance of the class has been created and returns the old instance if it already exists, or creates a new instance in the JVM and returns it.
public class SingletonClass { private static SingletonClass sSoleInstance; private SingletonClass(){} //private constructor. public static SingletonClass getInstance(){ if (sSoleInstance == null){ //if there is no instance available... create new one sSoleInstance = new SingletonClass(); } return sSoleInstance; }}Copy the code
We all know that in Java, if two objects are the same, their hashCode is the same. Let’s test that if all of the above singleton classes are implemented correctly, the same hash will be returned.
public class SingletonTester { public static void main(String[] args) { //Instance 1 SingletonClass instance1 = SingletonClass.getInstance(); //Instance 2 SingletonClass instance2 = SingletonClass.getInstance(); //now lets check the hash key. System.out.println("Instance 1 hash:" + instance1.hashCode()); System.out.println("Instance 2 hash:" + instance2.hashCode()); }}Copy the code
Here is the output log:
15:04:341 I/System.out: Instance 1 hash:247127865
15:04:342 I/System.out: Instance 2 hash:247127865
Copy the code
You can see that both instances have the same hashCode. So, that means the code above creates the perfect singleton class, right? Not at all.
Make singleton class reflection safe
In the singleton class above, you can create more than one instance through reflection. Java Reflection is a process that detects or modifies the runtime behavior of a class at runtime. New singleton class instances can be generated by modifying the visibility of the constructor at run time and creating instances through the constructor. Run the following code, does the singleton class still exist?
public class SingletonTester { public static void main(String[] args) { //Create the 1st instance SingletonClass instance1 = SingletonClass.getInstance(); //Create 2nd instance using Java Reflection API. SingletonClass instance2 = null; try { Class<SingletonClass> clazz = SingletonClass.class; Constructor<SingletonClass> cons = clazz.getDeclaredConstructor(); cons.setAccessible(true); instance2 = cons.newInstance(); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { e.printStackTrace(); } //now lets check the hash key. System.out.println("Instance 1 hash:" + instance1.hashCode()); System.out.println("Instance 2 hash:" + instance2.hashCode()); }}Copy the code
Here is the output log:
15:21:48.216 I/ system. out: Instance 1 hash:51110277 15:21:48.216 I/ system. out: Instance 2 hash:212057050Copy the code
Each instance has a different hashCode. Clearly this singleton class fails the test.
Solution:
To prevent reflection from causing singleton failures, throw a runtime exception when the constructor has been initialized and other classes have been initialized again. Let’s update SingletonClass.java.
public class SingletonClass {
private static SingletonClass sSoleInstance;
//private constructor.
private SingletonClass(){
//Prevent form the reflection api.
if (sSoleInstance != null){
throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
}
}
public static SingletonClass getInstance(){
if (sSoleInstance == null){ //if there is no instance available... create new one
sSoleInstance = new SingletonClass();
}
return sSoleInstance;
}
}
Copy the code
Make the singleton thread-safe
What happens if two threads try to initialize a singleton class almost simultaneously? Let’s test the following code where two threads are created almost simultaneously and getInstance() is called.
public class SingletonTester { public static void main(String[] args) { //Thread 1 Thread t1 = new Thread(new Runnable() { @Override public void run() { SingletonClass instance1 = SingletonClass.getInstance(); System.out.println("Instance 1 hash:" + instance1.hashCode()); }}); //Thread 2 Thread t2 = new Thread(new Runnable() { @Override public void run() { SingletonClass instance2 = SingletonClass.getInstance(); System.out.println("Instance 2 hash:" + instance2.hashCode()); }}); //start both the threads t1.start(); t2.start(); }}Copy the code
If you run this code multiple times, sometimes you’ll find that different threads have created different instances.
16:16:24.148 I/ system. out: Instance 1 hash:247127865 16:16:24.148 I/ system. out: Instance 2 hash:267260104Copy the code
This shows that your singleton class is not thread-safe. All threads call the getInstance() method at the same time, and the sSoleInstance == null condition returns a value for all threads, so two different instances are created. This breaks the singleton rule.
The solution
Synchronize the getInstance() method
public class SingletonClass {
private static SingletonClass sSoleInstance;
//private constructor.
private SingletonClass(){
//Prevent form the reflection api.
if (sSoleInstance != null){
throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
}
}
public synchronized static SingletonClass getInstance(){
if (sSoleInstance == null){ //if there is no instance available... create new one
sSoleInstance = new SingletonClass();
}
return sSoleInstance;
}
}
Copy the code
After we synchronize the getInstance() method, the second thread must wait until the first thread finishes executing the getInstance() method, which is thread-safe.
However, this approach also has some disadvantages:
- The overhead of locking results in slow execution
- Unnecessary synchronization operation after instance variable initialization
Double check the lock
Creating an instance using the double-checked lock approach overcomes the above problem.
In this approach, when the instance is empty, the singleton class is created in the synchronized code block so that the synchronized code block is executed only when sSoleInstance is empty, avoiding unnecessary synchronization operations.
public class SingletonClass { private static SingletonClass sSoleInstance; //private constructor. private SingletonClass(){ //Prevent form the reflection api. if (sSoleInstance ! = null){ throw new RuntimeException("Use getInstance() method to get the single instance of this class."); } } public static SingletonClass getInstance() { //Double check locking pattern if (sSoleInstance == null) { //Check for the first time synchronized (SingletonClass.class) { //Check for the second time. //if there is no instance available... create new one if (sSoleInstance == null) sSoleInstance = new SingletonClass(); } } return sSoleInstance; }}Copy the code
Use the volatile keyword
On the surface, this approach looks perfect, and you only need to pay the cost of a static block of code once. But singletons can still be broken unless you use the volatile keyword.
Without the volatile modifier, another thread might refer to the variable sSoleInstance while its initialization is incomplete. But with the happens-before guarantee for volatile, all writes to sSoleInstance occur before reads.
public class SingletonClass { private static volatile SingletonClass sSoleInstance; //private constructor. private SingletonClass(){ //Prevent form the reflection api. if (sSoleInstance ! = null){ throw new RuntimeException("Use getInstance() method to get the single instance of this class."); } } public static SingletonClass getInstance() { //Double check locking pattern if (sSoleInstance == null) { //Check for the first time synchronized (SingletonClass.class) { //Check for the second time. //if there is no instance available... create new one if (sSoleInstance == null) sSoleInstance = new SingletonClass(); } } return sSoleInstance; }}Copy the code
The above singleton class is now thread-safe. In multithreaded applications such as Android applications, it is necessary to ensure thread-safe singleton classes.
Make the singleton serialized safe
In distributed systems, there are cases where you need to implement the Serializable interface in a singleton class. This way you can store its state in the file system and retrieve it at a later point in time.
Let’s test whether this singleton class remains a singleton after serialization and deserialization.
public class SingletonTester { public static void main(String[] args) { try { SingletonClass instance1 = SingletonClass.getInstance(); ObjectOutput out = null; out = new ObjectOutputStream(new FileOutputStream("filename.ser")); out.writeObject(instance1); out.close(); //deserialize from file to object ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser")); SingletonClass instance2 = (SingletonClass) in.readObject(); in.close(); System.out.println("instance1 hashCode=" + instance1.hashCode()); System.out.println("instance2 hashCode=" + instance2.hashCode()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); I/ system. out: Instance 1 hash:247127865 16:16:24.148 I/ system. out: Instance 2 hash:267260104Copy the code
You can see that the instance hashCode is different, violating the singleton principle. After serializing the singleton class, a new instance of the class is created when we deserialize it. To prevent another instance, you need to provide an implementation of the readResolve() method. ReadResolve () instead reads objects from the stream. This ensures that no one can create new instances during serialization and deserialization.
public class SingletonClass implements Serializable {
private static volatile SingletonClass sSoleInstance;
//private constructor.
private SingletonClass(){
//Prevent form the reflection api.
if (sSoleInstance != null){
throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
}
}
public static SingletonClass getInstance() {
if (sSoleInstance == null) { //if there is no instance available... create new one
synchronized (SingletonClass.class) {
if (sSoleInstance == null) sSoleInstance = new SingletonClass();
}
}
return sSoleInstance;
}
//Make singleton from serialize and deserialize operation.
protected SingletonClass readResolve() {
return getInstance();
}
}
Copy the code
conclusion
At the end of the article, you can create thread-safe, reflective, and serializable singleton classes, but this is still not a perfect singleton, and you can use clones or multiple classloaders to create more than one instance. But for most applications, the above implementation works fine.
Article update on wechat public number: Bingxin said, focus on Java, Android original knowledge sharing, LeetCode solution, welcome to pay attention to!