Singleton is a very common design pattern, so it is necessary to understand its concept and several common writing methods, and this is often asked in the interview knowledge.
The singleton mode means that all requests are handled with a single object. For example, Spring uses singleton by default, while the multi-instance mode means that a new object is created for each request, such as actions in Structs2.
Using the singleton pattern ensures that there is only one instance of a class and that it is easy to access externally, as well as saving system resources. If you want only one object of a class in your system, you can use the singleton pattern.
So how do you make sure there’s only one instance of a class?
As we know, we usually create a new object with the new keyword. Class constructors are public, and you can create as many instances of the class as you like. So, first we need to make the constructor private, so that new objects cannot be created arbitrarily, thus controlling the creation of multiple instances arbitrarily.
Then, define a private static property to represent the instance of the class, which can only be accessed inside the class, not directly outside it.
Finally, the private static property is returned via a static public method, creating a globally unique access point for the system.
These are the three elements of the singleton pattern. Summarized as:
- Private constructor
- A private static variable that points to its own instance
- External static public access methods
Singleton patterns are divided into hungriness and slacker. The main difference is when objects are instantiated. Hanky-hank, where an object is instantiated when the class is loaded. Lazy, instantiate objects only when they are actually used.
Hanhanian singleton code implementation:
Private static Singleton = new Singleton(); private static Singleton = new Singleton(); Private Singleton(){} public static Singleton getInstance(){return Singleton; }}Copy the code
Lazy singleton code implementation
Public class Singleton {private static Singleton = null; private static Singleton = null; Public static Singleton getInstance(){if(Singleton == null){Singleton == null new Singleton(); } return singleton; }}Copy the code
Experienced programmers will see that the above lazy singleton implementation is fine under a single thread. However, if you use multiple threads, you will find that they may not return the same instance. We can verify this in code. Create 10 threads, start separately, thread to obtain the instance, the instance of the hashcode printed out, as long as the same is considered the same instance; If no, multiple instances are created.
public class TestSingleton { public static void main(String[] args) { for (int i = 0; i < 10 ; i++) { new MyThread().start(); } } } class MyThread extends Thread { @Override public void run() { Singleton singleton = Singleton.getInstance(); System.out.println(singleton.hashCode()); }} /** 668770925 668770925 649030577 668770925 668770925 668770925 668770925 668770925 668770925 668770925Copy the code
Therefore, the above lazy implementation is not thread-safe. What about hungry? You can test it manually and see that it returns the same Hashcode no matter how many times you run it. Therefore, hangry singleton is considered thread-safe.
So why is hungry thread-safe? This is because a hanchian singleton creates an instance of the class when the class is loaded, that is, before the thread accesses the singleton. A class is loaded only once in its lifetime. Therefore, it is possible to ensure that there is only one instance. So hungrier singletons are inherently thread-safe. (Take a look at the class loading mechanism.)
Since lazy singletons are not thread-safe, we need to modify them to work in multithreaded environments. Here are some common ways to write:
1) Use synchronized methods
The implementation is as simple as adding a synchronized keyword to the method
public class Singleton { private static Singleton singleton = null; Private Singleton(){} Public static synchronized Singleton getInstance(){if(Singleton == null){Singleton = new Singleton(); } return singleton; }}Copy the code
In this way, although thread safety can be ensured, the scope of synchronous method is too large, and the granularity of lock is relatively coarse, so the execution efficiency is relatively low.
2) synchronized
Since the scope of the entire method is large, I can narrow it down to the small block of code that creates the instance. (Because the method is simpler, there is no significant difference between the lock block and the lock method.)
public class Singleton { private static Singleton singleton = null; Private Singleton(){} public static Singleton getInstance(){//synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } return singleton; }}Copy the code
This approach is essentially the same as the first, so the efficiency gains are small enough to be negligible.
3) Double check
As you can see, the second method above simply calls the getInstance method and goes into the synchronized code block. Therefore, there will be an impact on efficiency. In fact, we can determine whether the instance already exists. If it exists, the instance has been created and there is no need to synchronize the code block. If it does not exist, it is empty before entering the synchronized code block, which can improve the execution efficiency. Therefore, there are the following double checks:
Public class Singleton {private static volatile Singleton = null; private static volatile Singleton = null; Private Singleton(){} public static Singleton getInstance(){} public static Singleton getInstance(){ If (singleton == null){synchronized (singleton == null){synchronized (singleton == null){ singleton = new Singleton(); } } } return singleton; }}Copy the code
It is important to note that static instance variables need to be volatile in this approach. Because new Singleton() is a non-atomic operation, the flow is:
A. Allocate memory space for singleton instance b. Call the constructor of singleton class to create instance C. Pointing the Singleton instance to the allocated memory space, the Singleton instance is not considered emptyCopy the code
The normal order is A -> B -> C, but the JVM sometimes reorders instructions to optimize the compiler. The order of execution is a-> C -> B. In multithreading, this would occur if thread 1 performed a new object operation and instruction reordering occurred, causing the Singleton instance to point to the allocated memory space (c), but in fact, the instance has not yet been created (b).
If (singleton == null) is false, thread 2 will return the singleton instance instead of synchronizing the block. If (singleton == null) is false, thread 2 will return the singleton instance instead of synchronizing the block.
4) Use static inner classes
Consider that since class loads are on demand and only loaded once, they are thread-safe, which is why hunchman singletons are inherently thread-safe. In the same way, we can define a static inner class to ensure that class attributes are loaded only once.
Public class Singleton {private Singleton(){} private static class Holder {private static Singleton class Holder {private static Singleton class Holder {private static Singleton class Holder {private static Singleton class Holder {private static Singleton class Holder = new Singleton(); } public static Singleton getInstance(){return holder.singleton; }}Copy the code
Also, the JVM does not load static inner classes when it loads external classes, but only when methods or attributes of the inner class (in this case, the Singleton instance) are called, so there is no wasted space.
5) Use enumerated classes
Because enumerated classes are thread-safe and only loaded once, you can use this feature to implement singletons through enumerated classes.
Public class Singleton {private Singleton(){} // Define an enumeration class private enum SingletonEnum {// Create an enumeration INSTANCE INSTANCE; private Singleton singleton; // Instantiate singleton class SingletonEnum(){singleton = new singleton (); } private Singleton getInstance(){ return singleton; }} public static Singleton getInstance () {/ / return SingletonEnum access to the Singleton INSTANCE. The INSTANCE. The getInstance (); }}Copy the code