Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
background
The singleton pattern is one of the more easily understood design patterns that I will not cover here. It ensures that there is only one instance of a class and provides a global access point.
The most classic implementation is as follows:
public class Singleton {
private static Singleton instance;
private Singleton(a) {}
public static Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
returninstance; }}Copy the code
So he’s got a problem. What’s the problem?
In a single-threaded environment, there is no problem with this code. However, in a multi-threaded environment, this may result in different threads creating different instances, so this is not a singleton pattern.
Instance = new Singleton(); instance = new Singleton(); instance = new Singleton(); instance = new Singleton(); .
Solution 1: Synchronization
The simplest and most straightforward way to solve this problem is to change the method to synchronized, which ensures that only one thread can enter the method at a time until it releases the lock.
public static synchronized Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
Copy the code
But do you feel like you’re overreacting? Because in reality this problem only occurs when an instance is created for the first time. Using this method causes multiple threads to execute the method mutually exclusive each time. In an environment with many, many threads, this can have a significant impact on performance.
Solution 2: Do not delay instantiation
If your application must use this singleton every time, or if it is not too burdensome to create (it is almost impossible to have multiple threads creating instances at the same time). The instance can then be created “eagerly” without delay.
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(a) {}
public static Singleton getInstance(a) {
returninstance; }}Copy the code
The JMM ensures that an instance of it is created before it becomes visible to any thread.
Solution 3: Double-check locking
public class Singleton {
private volatile static Singleton instance;
private Singleton(a) {}
public static Singleton getInstance(a) {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = newSingleton(); }}returninstance; }}Copy the code
Volatile ensures that writes to a variable are visible to reads of that variable. This means that one thread’s writes to the variable must be known by other threads’ reads to the variable.
Because of this feature, the first if (instance == null) {method is only executed the first time. After entering the synchronization code block, check again to ensure that the instance is null before creating it.
This method solves the problem of synchronized and greatly reduces the time consumption of getInstance().
conclusion
If you ensure only a single-threaded environment, you don’t need to make these solutions. If you have multiple threads and are infrequent, and the performance of getInstance() is not important to your application, then it makes sense to use scenario 1 synchronization. If you always need to create instances, it makes sense to use scenario 2 without delaying instantiation. Scenario 3 ensures synchronization while reducing the use of synchronization to improve performance, but the code is slightly more complex.
Therefore, it is the most reasonable to make different schemes for different scenarios.