I. overview
Singleton pattern is a relatively simple and common design pattern in design pattern, but it is also a very classic high-frequency interview question. I believe there are still many people hanging here during the interview. This article mainly reviews the singleton pattern, recording its application scenarios, common writing, debugging for thread safety (visible threads) and summarizing. I believe that after reading this article, you have a very deep understanding of the singleton model.
According to the common singleton pattern, the article explains the records from the shallow to the deep, and points out the deficiency of the writing method, so as to carry out the evolution and transformation.
Adhering to the principle of less nonsense, let's start quickly.
Two, definition
Singleton Pattern is to ensure that a class has absolutely only one instance in any case and to provide a global access point.
The singleton pattern is the creation pattern.
III. Application scenarios
- Examples in life: for example, the president of the country, the CEO of the company, the Department manager, etc.
- In the Java world: ServletContext, ServletContextConfig, etc.
- In Spring framework applications: Application Context, database connection pool are also singletons.
IV. Writing of Common Singleton Patterns
The main singleton modes are: hungry singleton, lazy singleton (thread insecurity, thread safety, double check lock type, static internal class type), registered singleton (enumerated singleton, container singleton), ThreadLocal thread singleton.
Let's take a look at the various modes of writing.
1. Hungry Chinese singleton
Hungry Chinese singletons are initialized immediately when classes are loaded, and singleton objects are created. Absolute thread security, which is instantiated before threads appear, is impossible to have access security problems.
The IOC container ApplicationContext in Spring is a typical hungry Chinese singleton.
Advantages and disadvantages
Advantages: No locks, high execution efficiency, in terms of user experience, better than lazy.
Disadvantage: Class is initialized when it is loaded. It takes up space whether it is used or not. It wastes memory and may take up the pit without shit.
Writing method
/** * @author eamon.zhang * @date 2019-09-30 9:26 a.m. */ public class HungrySingleton { // 1. Privatization constructor private HungrySingleton (){} // 2. Create self-instances within classes private static final HungrySingleton instance = new HungrySingleton(); // 3. Provide a way to get a unique instance (global access point) public static HungrySingleton getInstance(){ return instance; } }
There is another way of writing, using the mechanism of static code blocks:
/** * @author eamon.zhang * @date 2019-09-30 10:46 a.m. */ public class HungryStaticSingleton { // 1. Privatization constructor private HungryStaticSingleton(){} // 2. Instance variables private static final HungryStaticSingleton instance; // 3. Instance in static code blocks static { instance = new HungryStaticSingleton(); } // 4. Provide a method of obtaining examples public static HungryStaticSingleton getInstance(){ return instance; } }
Test code, we create 10 threads (specific thread launcher Concurrent Executor in the end source code to obtain):
/** * @author eamon.zhang * @date 2019-09-30 11:17 a.m. */ public class HungrySingletonTest { @Test public void test() { try { ConcurrentExecutor.execute(() -> { HungrySingleton instance = HungrySingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " : " + instance); }, 10, 10); } catch (Exception e) { e.printStackTrace(); } } }
Test results:
pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-10 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-7 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 pool-1-thread-8 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6 ...
As you can see, hungry Chinese style gets the same instance every time.
Usage scenarios
Both of them are very simple and very understandable. Hungry Chinese style is suitable for the case of fewer singletons.
Now let's look at the better way to write -- the lazy singleton.
2. Lazy Singleton
The characteristic of lazy singletons is that internal classes are loaded only when they are called by external classes.
The lazy singleton can be divided into the following several ways of writing.
Simple Slacker (Thread Insecurity)
This is a simple way to write lazy singletons.
/** * @author eamon.zhang * @date 2019-09-30 10:55 a.m. */ public class LazySimpleSingleton { private LazySimpleSingleton(){} private static LazySimpleSingleton instance = null; public static LazySimpleSingleton getInstance(){ if (instance == null) { instance = new LazySimpleSingleton(); } return instance; } }
Let's create a multithreading to test whether it's thread-safe:
/** * @author eamon.zhang * @date 2019-09-30 11:12 a.m. */ public class LazySimpleSingletonTest { @Test public void test() { try { ConcurrentExecutor.execute(() -> { LazySimpleSingleton instance = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " : " + instance); }, 5, 5); } catch (Exception e) { e.printStackTrace(); } } }
Operation results:
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@748e48d0 pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
From the test results, there is a certain probability of creating two different results, which means that the above singleton has thread security risks.
Why? Because of the length problem, we will use the test tool in the next article to conduct a detailed analysis (which may also be the interviewer's question in the interview). Now you just need to know that there is such a problem with the simple slacker style.
Simple lazy (thread safe)
Through the test of the simple lazy singleton above, we know that there are hidden thread security problems, so how to avoid or solve them?
We all know that in java there is a synchronized way to lock shared resources, to ensure that only one thread can get the resources at the same time, and other threads can only wait. So, we transform the simple slacker style above by adding synchronization to the getInstance() method:
/** * @author eamon.zhang * @date 2019-09-30 10:55 a.m. */ public class LazySimpleSyncSingleton { private LazySimpleSyncSingleton() { } private static LazySimpleSyncSingleton instance = null; public synchronized static LazySimpleSyncSingleton getInstance() { if (instance == null) { instance = new LazySimpleSyncSingleton(); } return instance; } }
Then test with a starter gun:
@Test public void testSync(){ try { ConcurrentExecutor.execute(() -> { LazySimpleSyncSingleton instance = LazySimpleSyncSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " : " + instance); }, 5, 5); } catch (Exception e) { e.printStackTrace(); } }
After several rounds of testing and observing the results, it is found that the same example can be obtained:
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
Thread security problem has been solved. However, when the number of threads is relatively large, if the CPU allocation pressure increases, a large number of threads will be blocked, which will lead to a significant decline in program performance.
So, is there a better way to balance thread security and improve program performance? The answer is yes.
Let's look at the single mode of double-checking locks.
Double Check Lock Lazy Man Style
The synchronized lock is locked on the getInstance() method. When multiple threads come to fetch resources, they actually need to take not the getInstance() method, but the instance instance object in the getInstance() method. If the instance object is initialized and multiple threads arrive, they can take advantage of the I in the method. F (instance == null) to determine whether to instantiate, if it has been instantiated and returned directly, there is no need to instantiate again. So the above code is modified:
First transformation:
/** * @author eamon.zhang * @date 2019-09-30 2:03 p.m. */ public class LazyDoubleCheckSingleton { private LazyDoubleCheckSingleton() { } private static LazyDoubleCheckSingleton instance = null; public static LazyDoubleCheckSingleton getInstance() { // The judgement here is to filter unnecessary synchronization locks, because if instantiated, you can go back directly. if (instance == null) { // If it is not initialized, the resources are locked and released after instantiation is completed. synchronized (LazyDoubleCheckSingleton.class) { instance = new LazyDoubleCheckSingleton(); } } return instance; } }
Can this method work? The answer is no, although the code adds synchronization locks to the instantiation operation, which solves the blocking caused by synchronization locks in each thread and improves performance; however, there is a problem here:
- Thread X and thread Y call the getInstance() method at the same time. They judge instance = null at the same time. The result is null, so they enter the if code block.
- At this point thread X gets control of the CPU - > enters the synchronous code block - > creates the object - > returns the object.
- Thread X executes, releases the lock, and thread Y gains control of the CPU. Similarly, -> Enter Synchronization Block - > Create Object - > Return Object
So we can clearly analyze that the LazyDoubleCheckSingleton class returns more than one instance! So the above code is not working! You can test yourself, I will not test here!
After analysis, because thread X has instantiated the object, when thread Y enters again, we can not solve the "this" problem by adding a layer of judgment. Indeed, look at the code:
/** * @author eamon.zhang * @date 2019-09-30 2:03 p.m. */ public class LazyDoubleCheckSingleton { private LazyDoubleCheckSingleton() { } private static LazyDoubleCheckSingleton instance = null; public static LazyDoubleCheckSingleton getInstance() { // The judgement here is to filter unnecessary synchronization locks, because if instantiated, you can go back directly. if (instance == null) { // If not initialized, the resources are locked and released after instantiation (note that multiple threads may enter at the same time) synchronized (LazyDoubleCheckSingleton.class) { // The if function here is: if the subsequent process gets the lock after the previous thread instantiates and enters the code block, // Obviously, resources have been instantiated, so judgment filtering is needed. if (instance == null) { instance = new LazyDoubleCheckSingleton(); } } } return instance; } }
Do you think this transformation is perfect? In our habitual "reasoning" mode of thinking, there seems to be nothing wrong with it, but the procedure is computer execution. What does that mean?
In instance = new Lazy Double Check Singleton (); when this code is executed, the internal computer is not a simple one-step operation, that is, non-atomic operation. In JVM, this line of code probably does a few things:
- Allocate memory to instance
- Initialize member variables by calling the constructor of Lazy DoubleCheckSingleton
- Point instance object to allocated memory space (instance is non-null after executing this step)
But there is an optimization of instruction reordering in JVM's instant compiler; in general, the order of the second and third steps above is not guaranteed. If the order of execution is 1 - > 3 - > 2, the instance is preempted by another thread A before the execution of the third and the execution of the second is not executed, so the instance is non-null (but not initialized), so it is online. Cheng A will return to instance directly, and then be called by the program, it will report an error.
Of course, this situation is very difficult to test, but there will be such a problem, so we must solve it, the solution is very simple, that is, j will instance plus volatile keyword.
So the relatively perfect way is:
/** * @author eamon.zhang * @date 2019-09-30 2:03 p.m. */ public class LazyDoubleCheckSingleton { private LazyDoubleCheckSingleton() { } private static volatile LazyDoubleCheckSingleton instance = null; public static LazyDoubleCheckSingleton getInstance() { // The judgement here is to filter unnecessary synchronization locks, because if instantiated, you can go back directly. if (instance == null) { // If not initialized, the resources are locked and released after instantiation (note that multiple threads may enter at the same time) synchronized (LazyDoubleCheckSingleton.class) { // The if function here is: if the subsequent process gets the lock after the previous thread instantiates and enters the code block, // Obviously, resources have been instantiated, so judgment filtering is needed. if (instance == null) { instance = new LazyDoubleCheckSingleton(); } } } return instance; } }
The test code is described at the end of the article.
Static Internal Lazy Man Style
The above double lock check form of singleton, for daily development, is indeed enough, but the use of synchronized keywords in the code, always lock, lock will have a performance problem. Is there no better plan? Let's not mention it. Let's consider it from the angle of class initialization. That's the way static inner classes are used here.
No more nonsense, just look at the code:
/** * * @author eamon.zhang * @date 2019-09-30 2:55 p.m. */ public class LazyInnerClassSingleton { private LazyInnerClassSingleton() { } // Pay attention to the keyword final to ensure that methods are not rewritten and overloaded public static final LazyInnerClassSingleton getInstance() { return LazyHolder.INSTANCE; } private static class LazyHolder { // Note the final keyword (guaranteed not to be modified) private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton(); } }
Multithread testing:
pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2 pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2 pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2 ...
The result is the same object instance.
conclusion
This method not only solves the problem of memory wasting caused by hungry Chinese, but also solves the performance problems caused by synchronized.
principle
The principle of using is the loading initialization order of classes:
- When the class is not called, the static inner class of the class will not be initialized, which avoids the problem of memory waste.
- When a method calls the getInstance() method, the static inner class is initialized first, and the member variable in the static inner class is final, so even if it is multi-threaded, its member variable will not be modified, so the performance problem caused by adding synchronized is solved.
First of all, thank you and congratulate you for seeing this, because I want to tell you that there seems to be a small problem with all the above singular models - violence and destruction. The solution to this problem is the enumeration type singleton mentioned below.
As for the reasons and why enumeration can solve this problem, similarly, the space reasons will be explained in a separate article later.
Now let's talk about the registered list.
3. Registered (registered) singletons
Registered singleton, also known as registered singleton, is to register each instance to a certain place and use a unique identifier to obtain the instance.
Registered singletons are written in two ways: container cache and enumeration register.
Let's first look at how to write enumerative singletons.
Enumeration single case
Say less nonsense, look directly at the code, let's first create the EnumSingleton class:
/** * @author eamon.zhang * @date 2019-09-30 3:42 p.m. */ public enum EnumSingleton { INSTANCE; private Object instance; EnumSingleton() { instance = new EnumResource(); } public Object getInstance() { return instance; } }
Look at the test code:
/** * @author eamon.zhang * @date 2019-09-30 3:47 p.m. */ public class EnumSingletonTest { @Test public void test() { try { ConcurrentExecutor.execute(() -> { EnumSingleton instance = EnumSingleton.INSTANCE; System.out.println(instance.getInstance()); }, 10, 10); } catch (Exception e) { e.printStackTrace(); } } }
Test results:
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7 com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
The results are the same, which shows that enumerated class singletons are thread-safe and non-destructive; the syntactic particularity of JDK enumeration and reflection also protect enumeration, making enumerated singletons a more elegant implementation.
Enumeration class singletons are also recommended in Effective Java.
Container singleton
Registered singletons have another way of writing, using container caching to look directly at the code:
Create the ContainerSingleton class:
/** * @author EamonZzz * @date 2019-10-06 18:28 */ public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>(); public static Object getBean(String className) { synchronized (ioc) { if (!ioc.containsKey(className)) { Object object = null; try { object = Class.forName(className).newInstance(); ioc.put(className, object); } catch (Exception e) { e.printStackTrace(); } return object; } else { return ioc.get(className); } } } }
Test code:
@Test public void test() { try { ConcurrentExecutor.execute(() -> { Object bean = ContainerSingleton .getBean("com.eamon.javadesignpatterns.singleton.container.Resource"); System.out.println(bean); }, 5, 5); } catch (Exception e) { e.printStackTrace(); } }
Test results:
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
Container writing is suitable for creating many instances and easy to manage. However, it is non-thread-safe.
In fact, Spring also has the relevant container STANLEY's implementation code, such as AbstractAutowireCapableBeanFactory interface.
So far, the introduction of registered singletons has been completed.
Five, expand
ThreadLocal Thread Singleton
ThreadLocal can't guarantee that the objects it creates are unique, but it can guarantee that they are unique in a single thread and that they are inherently thread-safe in a single thread.
Look at the code:
/** * @author EamonZzz * @date 2019-10-06 21:40 */ public class ThreadLocalSingleton { private ThreadLocalSingleton() { } private static final ThreadLocal<ThreadLocalSingleton> instance = ThreadLocal.withInitial(ThreadLocalSingleton::new); public static ThreadLocalSingleton getInstance() { return instance.get(); } }
Test procedures:
@Test public void test() { System.out.println("-------------- Single thread start ---------"); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println("-------------- Single thread end ---------"); System.out.println("-------------- Multithreading start ---------"); try { ConcurrentExecutor.execute(() -> { ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + " : " + singleton); }, 5, 5); } catch (Exception e) { e.printStackTrace(); } System.out.println("-------------- Multithreading end ---------"); }
Test results:
-------------- Single thread start --------- com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda -------------- Single thread end --------- -------------- Multithreading start --------- pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@2f540d92 pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@3ef7ab4e pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@604ffe2a pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@50f41c9f pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@40821a7a -------------- Multithreading end ---------
From the test results, it is not difficult to find that no matter how many calls are made in the main thread, the instances are the same; in the multi-threaded environment, each thread gets different instances.
Therefore, in a single-threaded environment, ThreadLocal can achieve the purpose of singletons. This is actually a space-for-time separation of threads.
Six, summary
Singleton mode can ensure that there is only one instance in memory, reduce memory overhead, and avoid wasting resources.
The singleton model seems very simple and easy to implement, but it is a high-frequency interview question. I hope you can understand it thoroughly.
The source code involved in this article is: