Article directory
1 Introduction
A design pattern is a methodology commonly used in software development to solve complex problems. It provides a proven set of solutions for the design and implementation of specific types of problems. Design patterns help developers improve code reusability, maintainability, and scalability.
There are many design patterns, this article mainly introduces the singleton pattern.
2. Singleton mode
The singleton pattern is a creational design pattern that guarantees that a class has only one instance and provides a global access point to obtain that instance.
3. How to ensure that a class has only one instance
In Java, static
keywords are often used to ensure that a class has only one instance.
In the singleton pattern, 类的构造函数被私有化
, to prevent external code from directly creating instances. Then, get a unique instance of the class through a static method or static variable .
If the instance does not exist, a new instance is created and returned; if the instance already exists, the instance is returned directly.
Instead, there are many ways to achieve the above. Here are three common implementation modes:
- Hungry Chinese style
- Lazy
- static inner class
4. Hungry Chinese style singleton mode
Hungry-style singleton: Create an instance when the class is loaded to ensure that there is an instance available under any circumstances.
Code example:
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
// 私有化构造函数
}
public static Singleton getInstance() {
return instance;
}
}
Code analysis:
The privatized constructor makes it impossible for the outside to directly create an instance. If other threads want to get Singleton
an instance of the class, they can only get it through the method Singleton
provided by the class getInstance()
.
5. Lazy singleton pattern
Lazy Singleton: Lazily load instances and create instances only when needed.
Code example:
public class SingletonLazy {
private static SingletonLazy instance = null;
private SingletonLazy(){
// 私有化构造方法
}
public static SingletonLazy getInstance(){
if (instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
Code analysis:
If the method is not called in the subsequent code getInstance
, then the step of creating an instance is saved.
In the above code, which way to implement the singleton mode is thread-safe?
The answer is the hungry man mode
Hungry mode does not involve modification, while lazy mode involves reading and modification. Then it is not safe in multi-threaded mode.
Imagine a scenario, if in lazy mode, two threads call the getInstance method at the same time, if one thread reads the instance is null, and creates an instance for the instance, while the instance read by the other thread is still null, and then An instance is created for the instance. Then the instance is created multiple times
6. Implement thread-safe Lazy Singletons
Since the lazy man mode is not safe in a multi-threaded environment, what if the lazy man mode is guaranteed to be safe in a multi-threaded environment?
Since it involves multi-threading, it is inseparable from "lock", that issynchronized
Example:
Through locking, the thread safety problem is solved , but it brings new problems.
Lazy singletons are only getInstance
thread-unsafe when the method is called for the first time. Once created, they are thread-safe.
However, the above method of locking will cause the thread to still need to be locked when it is already safe. 有些多此一举了
And the cost of locking is relatively large, which affects efficiency.
So what if there is no lock when thread safety is guaranteed?
Before the instance is created, the thread is not safe. After the instance is created, the thread is safe. Then a judgment can be made before the locking operation.
public class SingletonLazy {
private static SingletonLazy instance = null;
private SingletonLazy(){
}
public static SingletonLazy getInstance(){
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
}
Code analysis:
Use 双重if
to ensure that the instance is created only once and that it is not locked when the thread is safe. Here we need to focus on understanding.
But thinking carefully about the above code there is still a problem .
If two threads call getInstance
the method at the same time, the first thread gets the lock, enters the second layer if, and starts to create new objects.
The new operation here can be divided into:
- Apply for memory and get the first address of memory
- Call the constructor to initialize the instance
- Assign the first address of the memory to the instance reference
For the above-mentioned new operation, the compiler may perform "instruction reordering". This may cause 无序写入
problems
The order of 2 and 3 in the above steps can be changed in a single-threaded environment without affecting the result.
But it can cause problems in a multi-threaded environment 无序写入
:
- When thread A executes the instance = new Singleton(); line of code, due to the reordering of instructions, it may first perform any combination of the three operations of allocating memory space, initializing instance objects, and pointing instance to memory space, instead of following the code Execute sequentially.
- Assuming that thread A has finished allocating memory space and initializing instance objects, but has not pointed instance to the memory space, thread B executes the first instance == null judgment at this time, and the result is false, and returns instance directly. But the instance is not properly initialized at this time.
In order to solve the above problems, keywords can be used volatile
. It can prohibit instruction reordering optimization and ensure that the write operation of the instance precedes the read operation, thereby preventing other threads from obtaining the instance when the instance is not properly initialized.
public class SingletonLazy {
private static volatile SingletonLazy instance = null;
private SingletonLazy(){
}
public static SingletonLazy getInstance(){
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
}
The above is a completely safe way of writing a lazy singleton mode in a multi-threaded environment.
7. Static inner class implements singleton pattern
In addition to the above methods, the singleton mode can also be implemented using static inner classes.
This method uses the characteristics of static inner classes to achieve lazy loading and thread safety.
Code example:
public class Singleton {
private Singleton() {
// 私有化构造函数
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
The static inner class SingletonHolder holds the only instance of Singleton. When the getInstance method is called for the first time, the initialization of SingletonHolder is triggered to create an instance.
Since the initialization of the static inner class is thread-safe, it can be ensured that only one instance is created in a multi-threaded environment.
8. Summary
This article mainly introduces three ways to realize the singleton mode, the hungry style, the lazy style and the static inner class. Among them, the lazy style singleton is very important, and we must focus on understanding the meaning of double if.
It should be noted that the above methods can ensure the correct creation of singletons in a multi-threaded environment, but in special cases, such as using mechanisms such as reflection or serialization/deserialization, the uniqueness of singletons may still be destroyed.
In practical applications, it is necessary to select an appropriate implementation of the singleton mode according to specific scenarios and requirements.
Thank you for watching! I hope this article can help you!
Column: "Java Learning Journey from Scratch" is constantly being updated, welcome to subscribe!
"Wish to encourage you and work together!"