Once in an interview, WHEN talking about singleton pattern, I was still vague. I thought I understood it, but I got stuck at a certain node, so I decided to write an article about singleton pattern
What is the singleton pattern?
Singleton pattern: A class has only one instance and its own instantiation is provided to the entire system
So why do we need singletons?
To save RESOURCES for the JVM, we need to allocate memory in the heap for each object we create, which is limited, and in some cases we don’t always need that many objects. For example, if you format a date with SimpleDateFormat multiple times in a Controller method, you don’t need to create an object of the SimpleDateFormat class multiple times. The applyPattern() method of SimpleDateFormat sets the format.
Hunger-handedness of singleton pattern
public class SingleTon{
private static SingleTon singleton = new SingleTon();
private SingleTon(){
}
public static SingleTon getInstance(){
return singleton;
}}
Copy the code
Why is it called hungry? Because you create objects first, not when you need them. So how is this form of singleton guaranteed to be singleton? This is because of the nature of static variables. When is a static variable initialized? Which brings us to the life cycle of a class. What is the life cycle of a class? When you create objects using static methods or variables of a class, the bytecode of the corresponding class is loaded into memory.
Class life cycle
- Class to find and load bytecode
- Joins determine relationships between classes:
- Verify correctness
- During the preparation phase, num=0 and then 0 is changed to 10. During the preparation phase, there are only classes in the JVM, and no objects
- During the parsing phase, the JVM can map the full class name to the actual memory address, replacing the full class name with the memory address
- Initialization assigns the correct value to a static variable. Explicit assignment
- Use: object initialization, object garbage collection, object destruction
- Uninstall JVM to end the class life cycle: – normal end – exception end/error – system.exit (); – The operating system is abnormal
There is a definite order between the loading, validation, preparation, initialization, and unloading phases, and the loading process of a class must follow this order in a step-by-step “start “(just start, not execution or end, because these phases are usually intermixed. (This is a quote from the Deep Understanding of the Java Virtual Machine (version 2), but there is still a bit of confusion about why it is interleaved).
We notice that during class initialization, static variables of the class are explicitly assigned.
When can class initialization be triggered?
The following conditions are strictly defined for VMS in the initialization phase: If a class is not initialized, the class is initialized. The class is initialized only once.
- Creating an instance of the class corresponds to the new instruction in the bytecode
- To access static variables of the class (except constants that are final and determined at compile time), the corresponding bytecode instruction is getStatic
- Access the static method invokestatic of the class
- reflection
- When a class is initialized and its parent is not initialized, the initialization of the parent is triggered first
- When the virtual machine starts, the class that defines the main method is initialized first.
(I have a quick question here, how does the JVM know if a class has been initialized? What determines whether a class is initialized or not? Since classes are initialized only once, that means static variables are initialized explicitly only once.
Conclusion: This hanhanian singleton is guaranteed by explicitly initializing static variables only once
Simple slob of the singleton pattern (thread-unsafe version)
public class SimpleSingleTon {
private static SimpleSingleTon singleton = null;
private SimpleSingleTon(a){}public static SimpleSingleTon getInstance(a){
if (singleton == null){
singleton = new SimpleSingleTon();
}
returnsingleton; }}Copy the code
The disadvantage of this creation singleton pattern is that it is not secure in multithreading. Why? A lot of articles, when I get to this point, Speak to if there are two threads at the same time call getInstance () method, I personally think that this is some problem, we know that the CPU time slice to thread distribution, at the same time there will be only one thread to get the executive power of the CPU, said at the same time, personally, I feel like there are two threads at the same time won the executive power of CPU, I I don’t think it’s appropriate.
The insecurity of this singleton pattern lies in: Let’s say I have two threads A and B, When A executed the null statement, the time slice ran out, and B obtained the execution right of the CPU. When B also executed the null statement, it found that the Singleton was still null, and then an object was returned by the downward execution. At this time, the EXECUTION right of the CPU was returned to thread A, and thread A still remembered that the Singleton was empty, and it also new one Object out. Two threads return two different objects.
Let’s test it out:
public static void main(String[] args) {
// Use the built-in thread pool
ExecutorService pool = Executors.newFixedThreadPool(100);
for (int i = 0; i < 10000 ; i++) {
pool.execute(()->System.out.println(SimpleSingleTon.getInstance()));
}
pool.shutdown();
}
Copy the code
You have to increase the concurrency to test it out, which I did three times:
It’s not safe! I locked and Synchronized walked
You only need to add synchronized to the method to ensure that, but the larger the scope of lock, the worse the performance, as to why? There will be a separate section on synchronized so we’ll add it to the code block.
public class SimpleSingleTon {
private static SimpleSingleTon singleton = null;
private SimpleSingleTon(a){}public static SimpleSingleTon getInstance(a){
if (singleton == null) {synchronized(SimpleSingleTon.class){
// The thread is put to sleep because, without sleep, all that comes out is an instance.
try {
Thread.currentThread().sleep(100);
}catch (Exception e ){
e.printStackTrace();
}
singleton = newSimpleSingleTon(); }}return singleton;
}
public static void main(String[] args){
new Thread(()-> System.out.println(SimpleSingleTon.getInstance())).start();
new Thread(()-> System.out.println(SimpleSingleTon.getInstance())).start();
new Thread(()-> System.out.println(SimpleSingleTon.getInstance())).start();
newThread(()-> System.out.println(SimpleSingleTon.getInstance())).start(); }}Copy the code
Let’s analyze why: Let’s say I have two threads A and B, When A executed the null statement, the time slice ran out, and B obtained the execution right of the CPU. When B also executed the null statement, it found that the Singleton was still null, and then an object was returned by the downward execution. At this time, the EXECUTION right of the CPU was returned to thread A, and thread A still remembered that the Singleton was empty, and it also new one Object out. Two threads return two different objects. Then add a nuller after entering the lock. But there is still an instruction reordering problem.
What does instruction reorder mean?
In short, we write Java code that ultimately has to be interpreted as CPU instructions, but to speed up the execution of instructions, the CPU may not execute them sequentially. This is a profound problem, worth repeated discussion,new object corresponding instructions are about three: 1. Initialize the object. 3. Assign the address space of memory to the reference.
When the CPU actually executes the instructions, it can be 1-2-3 or 1-3-2. 1-2-3 is fine, of course, and 1-3-2 yields an incomplete instance. So you get an incomplete instance. You might have a problem with it when you use it.
So how do you measure that? We need something that’s a little bit more complicated to initialize, but I haven’t figured it out yet.
To prevent 1-3-2, we need to add Volatile variables to prevent reordering.
public class SimpleSingleTon { private static volatile SimpleSingleTon singleton = null; private SimpleSingleTon(){ } public static SimpleSingleTon getInstance(){ if (singleton == null){ synchronized(SimpleSingleTon.class){ try { Thread.currentThread().sleep(100); }catch (Exception e ){ e.printStackTrace(); } singleton = new SimpleSingleTon(); } } return singleton; }}Copy the code
Volatile can be dealt with in its own right. That’s another story.
Lazy style of static inner class
public class SimpleSingleTon { private SimpleSingleTon(){ } private static class LazySingleTon{ private static final SimpleSingleTon INSTANCE = new SimpleSingleTon(); } public static SimpleSingleTon getInstance(){ return LazySingleTon.INSTANCE; }}Copy the code
So how does this notation guarantee that the instances we get are the same? The same is true for statically triggering class initialization in the calling class, as we said earlier. Classes are initialized only once.
Introduction to the enumeration
Why enumerations here? Because the singleton pattern of enumeration is the most simple, the most extreme, the avenue to simplicity.
What is enumeration?
Personally, enumerations are a container of constants, so one might ask, why don’t you put constants in a collection? Because these constants are used by the global class. So what is it in Java? Let’s create a new enumeration and disassemble its bytecode with the javap -c command:
public enum EnumDemo {
HIGH,
LOW;
}
Copy the code
Notice that when our enumeration becomes bytecode, it becomes a class, or itself a class. Inheriting from the Enum class, we write static and final before HIGH and LOW, we write variable names in enumerations, in fact the JVM does this for us. The HIGH you write is an instance of an enumerated type.
Note that we wrote a variable that the JVM added to static and later helped us initialize. The singleton pattern of enumeration also uses the principle that static variables are loaded only once to ensure instance uniqueness.
How does it work? And features?
From a class perspective, you are a class that has inherited an Enum class. Java only supports single inheritance, so you cannot inherit from another class. But you can implement interfaces, but I don’t recommend you do that.
You can implement interfaces
public enum EnumDemo implements Runnable{
/**
*
*/
HIGH,
LOW;
@Override
public void run() {
}
}
Copy the code
You can define member variables and constructors and methods
I still can’t see which instruction in the bytecode calls the superclass constructor. But it does call. The enumeration code is the same as above, but the Runnable interface is not implemented.
We know that when we initialize an object of a subclass, we must first call the constructor of the parent class. Since all Enum types are subclasses of Enum, we will break the constructor of Enum:And then when this class goes over,I’m passing in the name of the variable that we wrote.
Defining member variables
Undeclared member variables actually enumerate instances of classes, and you call your custom constructor with parentheses
public enum EnumDemo{
/ * * * * /
HIGH("Monday".1),
LOW("2".3);
String key;
int value;
EnumDemo(String key, int value) {
this.key = key;
this.value = value; }}Copy the code
It’s worth pointing out the ways:
- ToString is overwritten:
Print the name of an instance of an enumeration (that is, a variable that you write in an enumeration class without declaring a type). How to iterate: When we write enumerations, the compiler automatically adds a static method values(). This method returns the constants you defined in the enumeration.
In summary, basically most singleton patterns are guaranteed by having static variables initialized only once. The singleton pattern can be written in the following ways: 1. Full Han type (thread unsafe version): need to use when the new 3. Static inner class 5. enumeration
Main reference:
Wechat public account java3y singleton mode mp.weixin.qq.com/s/dU_Mzz76h… An in-depth understanding of the Java Virtual Machine (Version 2)