Definition of the singleton pattern in Java: “A class has one and only one instance, and its own instantiation is provided to the entire system.”
Basic implementation idea of singleton mode:
(1)
Privatize the constructorTo prevent the outside world from instantiating the object by calling the constructor, which is the private method.
Privatize the constructorTo prevent the outside world from instantiating the object by calling the constructor, which is the private method.
(2) Provide one
Public static methodsYou can get a unique instance of the class only through the static methods that the class provides, which are static methods.
Public static methodsYou can get a unique instance of the class only through the static methods that the class provides, which are static methods.
Several ways of writing singletons:
1. Hungry (thread safe)
Advantages:
The simple way to write this is to instantiate the class when it is loaded. To avoid the
Thread synchronizationThe problem.
Thread synchronizationThe problem.
Disadvantages:
Instantiation is done when the class is loaded, rather than lazy loading. If the instance is never used, then memory is wasted.
2. Lazy (threads are not safe)
Advantages:
This implementation method is compared with the previous implementation
Lazy loading (delayed loading)The effect of,
Lazy loading (delayed loading)The effect of,
Disadvantages:
Limitations: This implementation can only be implemented in
Single threadFor use in the environment. This is generated when a thread enters an if (singleton == null) statement and another thread passes the statement before it can proceed
More than one instance. This is not recommended and should not be used in multi-threaded environments.
Single threadFor use in the environment. This is generated when a thread enters an if (singleton == null) statement and another thread passes the statement before it can proceed
More than one instance. This is not recommended and should not be used in multi-threaded environments.
3. Lazy 2.0 (Thread-safe, synchronous approach)
Advantages:
To solve the problem of thread insecurity in the previous implementation, the solution is to make a
Thread synchronizationThe getInstance() method is then synchronized to the thread, which locks the getInstance() method to ensure that the singleton is unique.
Thread synchronizationThe getInstance() method is then synchronized to the thread, which locks the getInstance() method to ensure that the singleton is unique.
Disadvantages:
It’s too inefficient, each thread executes the getInstance() method whenever it wants to get an instance of the class.
4. Double check lock [not recommended]
Advantages:
The concept of double-check is familiar to multi-threaded developers, and as shown in the code, we perform two if (Singleton == null) checks to ensure thread-safety. If (singleton == null), return to instantiate the object. In this way, the safety of the thread is ensured, the efficiency is greatly improved, and the effect of lazy loading is realized.
In this way, we implement the singleton pattern in a very clever way. So far, everything looks perfect.
5. Double check lock (improved)
Before we look at this implementation, it is important to mention:
The volatile keywordThe concept of volatile is not to be discussed at length. Here are two characteristics of volatile:
Order + visibility.
The volatile keywordThe concept of volatile is not to be discussed at length. Here are two characteristics of volatile:
Order + visibility.
There is an important element in the compilation principle
Compiler optimization. Compiler optimization is the ability to pass without changing the original semantics
Adjust statement orderTo make the program run faster.
Compiler optimization. Compiler optimization is the ability to pass without changing the original semantics
Adjust statement orderTo make the program run faster.
For example: What steps are required to create a variable?
1: Apply for a block of memory.
2: Call constructor to initialize,
3: Allocate a pointer to the memory block.
Is there a sequence for these operations? In the JVM it is not. Can we assume that the JVM first opens a block of memory, then points to it, and then calls the constructor to initialize it?
Then, for the above implementation: there may be cases like this:
Singleton == null and synchronized (singleton. Class) {} code block is synchronized (singleton. If thread A does not have time to perform A new, the time slice is up. If thread A does not have time to perform A new, the time slice is up.
Due to the existence of compiler optimizations, we want to create A variable step is 1 – > 2 – > 3, but because the compiler optimization, so the compiler, possible code execution order is 1 – > 3, before are initialized () in the second step, thread B may time out, thread A came in again, Thread B’s new instance is not initialized, but is simply a singleton pointer pointing to the memory region.
The problem is that even though the Singleton is not null, it is not constructed. If B uses the singleton instance before A completes the construction, the object will be uninitialized.
Specifically, synchronized guarantees atomicity, but does not guarantee the correctness of instruction reordering. In other words, our threads are atomicity, but the program may be executing on a multi-core CPU, which could lead to this situation.
After JDK 1.5, Java used a new memory model. The volatile keyword has explicit semantics (prior to JDK1.5, volatile was a keyword, but its purpose was not specified). Volatile variables cannot be adjusted with previous or subsequent reads. (Learn the Java Happen -before principle/memory model). Therefore, we could have simply added the volatile singleton and solved the problem perfectly
6, static internal class/register
We mentioned that volatile was defined after JDK 1.5, but how was it implemented before that? This is the next solution — static inner classes — that doesn’t depend on JDK versions.
Static inner class mode when the Singleton class is loaded
It is not instantiated immediatelyInstead, the Singleton instantiation is completed by calling getInstance to load the SingletonHolder class.
It is not instantiated immediatelyInstead, the Singleton instantiation is completed by calling getInstance to load the SingletonHolder class.
The static properties of a class are initialized only when the class is first loaded, so the JVM helps us keep our threads safe from entering while the class is initialized. This technique is explicitly specified by the JVM, so there is no ambiguity.
7, Enumeration method
public enum Singleton
{ INSTANCE; }
Enum classes cannot be inherited either, and in decompilation we find that the class is final.
Enum has and only has private constructors, preventing additional constructs from outside, which fits nicely with the singleton pattern.
Enumeration singletons are performed but the compiled result is:
Class SingleTon extends enum{
Public static final SingleTon singleTon;
}
8. You can avoid creating multiple objects through reflection and deserialization. Thread safety.
Q: How do Java enumerations keep threads safe?
A: Because Java class loading and initialization is thread-safe for the JVM, and Java enums are essentially final classes after the compiler compiles the bytecode. Each enum type is a static constant property in that final class. Its properties are initialized in the static block of the final class, and static constant properties and code blocks are initialized at class load time, so naturally the JVM guarantees concurrency safety.
Q: Why do some people say that singletons implemented through enumerations are the best approach in some scenarios?
A:In fact, this topic is to kill two birds with one stone, both examine the essence of Java enumeration and examine some of the disadvantages of the singleton pattern. A big problem with implementations other than the singleton pattern is that they are no longer singletons once the Serializable interface is implemented, Because each call to the readObject() method returns a newly created object (which can be avoided by using the readResolve() method, of course, but is always a hassle), and the Java specification guarantees that each enumerated type and the enumerated variables it defines are unique within the JVM, Java takes special care of serialization and deserialization of enumerated types,
In serialization, Java simply prints the enumeration object’s name property into the result. In deserialization, Java finds the enumeration object by name using the valueOf method of java.lang.Enum. The writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods are disabled.
In serialization, Java simply prints the enumeration object’s name property into the result. In deserialization, Java finds the enumeration object by name using the valueOf method of java.lang.Enum. The writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods are disabled.
9. Application of singleton pattern
China can only be led by the Communist Party of China. (Haha, just kidding)
2. The Windows Task Manager is a typical singleton. Can we open two Task managers? You can try it if you don’t believe me.
3. Windows recycle bin is also a typical singleton application. The recycle bin maintains only one instance throughout the entire system operation
4. The design of database connection pool is also generally a singleton pattern, because database connection is a database resource. The use of database connection pool in database software system is mainly to save the efficiency loss caused by opening or closing database connection. This efficiency loss is very expensive, because it can be greatly reduced by using singleton mode to maintain.
5. The design of multi-threaded thread pool generally adopts singleton mode, because thread pool should be convenient to control the threads in the pool.
6. The singleton mode is also used to read configuration objects of Web applications, because configuration files are shared resources.
7. The file system of an operating system is also a concrete example of a large singleton implementation. An operating system can only have one file system.