no_mkd
I. What is the Singleton Pattern?
The Singleton Pattern, also known as the Single Instance Pattern, simply put, is designed to ensure “only one instance of this class exists”
The Singleton Pattern is frequently used to manage resource-sensitive objects, such as: database connection objects, registry objects, thread pool objects, etc. Having multiple instances of such objects simultaneously would cause various inconsistent troubles (you surely don't want database reconnection exceptions to happen, right?)
II. How to Ensure Only One Instance of a Class?
(This question seems simple, but if you haven't encountered the Singleton Pattern before, coming up with a solution requires some talent..Try to think about it..)
1. Can a class instance be limited to just one? Apparently, you can just call new with the class name, right?
Of course you can. Knowing the class name allows you to call its constructor to create an instance. However, note one thing: the class must have a public constructor to create an instance from outside, right?
2. So to ensure a class has only one instance, the class shouldn't have a public constructor, right?
Exactly. That's correct. We need to define a private constructor
3. Can a class without a public constructor produce instances? If the constructor is private, only instances of this class can call this constructor. Similarly, to call this constructor to produce an instance of this class.. Isn't this a “chicken or egg” problem? Of course, a private constructor can produce instances. One point was overlooked above: it's not that “only instances of this class can call this constructor.” Because within the class itself, this private constructor can be called freely, without creating any instances
With the discussion above, we can implement the classic Singleton Pattern:
package SingletonPattern;/**
-
@author ayqy
-
The most classic Singleton Pattern */ public class Singleton {
private static Singleton instance;//Define static instance variable
/**
- Define private constructor to prevent creating instances from outside */ private Singleton(){ //Initialization operations }
/**
- Provide global access point
- @return Instance of this class */ public static Singleton getInstance(){ if(instance == null) instance = new Singleton(); return instance; }
/*
- Other useful attributes and behaviors
- After all, a class applying the Singleton Pattern still retains its original functionality
- */ }
Note: It's crucial to remember the last point—a class applying the Singleton Pattern should not lose its original functionality. Never use it just for the sake of using it
III. Continue Thinking About Our Singleton Pattern
Is our Singleton Pattern foolproof? No, it still has many issues, such as:
1. In multi-threaded environments; 2. In environments with multiple class loaders
We cannot guarantee only one instance is produced, right?
However, as a mature design pattern, the Singleton Pattern must be able to handle these environments gracefully. So next, we'll discuss how to cope with these environments
IV. Singleton Pattern in Multi-threaded Environments
How to ensure instance uniqueness in a multi-threaded environment? It's easy to think of using the synchronized keyword to ensure thread safety, like this:
/**
* Provide global access point
* @return Instance of this class
*/
public static synchronized Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instance;
}
We define the getInstance method as a synchronized method, which ensures no multiple threads can enter this method simultaneously, thus preventing different instances from being produced
However, the method above has a fatal problem: synchronizing methods with the synchronized keyword greatly reduces efficiency (synchronizing a method can even cause a hundred-fold efficiency drop..), which can bog down our program. Is there a better improvement?
First, the synchronized block above is the entire getIntance method. Every call to this method forces entry into the synchronization mechanism. But upon reflection, we only need synchronization during the first call to this method (the first time new-ing the object). Subsequent calls can directly return the already created object
So, our improvement plan is: Use double-checked locking to ensure synchronization only occurs during the first object creation
package SingletonPattern;/**
-
@author ayqy
-
Singleton Pattern under multi-threading 2——Using double-checked locking to ensure synchronization only during variable instantiation */ public class DoubleLockSingleton {
private static volatile DoubleLockSingleton instance;//Define static instance variable
/**
- Define private constructor to prevent creating instances from outside */ private DoubleLockSingleton(){ //Initialization operations }
/**
- Provide global access point
- @return Instance of this class */ public static DoubleLockSingleton getInstance(){ if(instance == null) synchronized(DoubleLockSingleton.class){//Enter synchronized block if(instance == null)//Check again instance = new DoubleLockSingleton(); } return instance; }
/*
- Other useful attributes and behaviors
- After all, a class applying the Singleton Pattern still retains its original functionality
- */ }
Note: Double-checked locking is reflected in the volatile keyword (telling the compiler this variable cannot keep a copy; once changed, it forces write-back, avoiding inconsistency) and the synchronized block
But understand the cost: the volatile keyword also tells the compiler not to perform compilation optimization on this object. Looking only at the first object creation process, double-checked locking's efficiency is even lower than synchronized methods. However, in subsequent calls, double-checked locking no longer requires synchronization, so in the long run, double-checked locking is more efficient than synchronized methods
Is there a way to ensure thread safety without using the slow synchronization mechanism? If so, it could greatly improve efficiency, right?
Of course there is. This method is called “eager initialization” (by the way, the “classic Singleton Pattern” mentioned at the beginning actually used “lazy initialization”.. very simple, no need to explain). Let's take a look:
package SingletonPattern;/**
-
@author ayqy
-
Singleton Pattern in multi-threaded environments——Using “eager initialization” to ensure thread safety */ public class EagerlyInitSingleton {
private static EagerlyInitSingleton instance = new EagerlyInitSingleton();//Define static instance variable, and initialize when the class is loaded
/**
- Define private constructor to prevent creating instances from outside */ private EagerlyInitSingleton(){ //Initialization operations }
/**
- Provide global access point
- @return Instance of this class */ public static synchronized EagerlyInitSingleton getInstance(){ return instance; }
/*
- Other useful attributes and behaviors
- After all, a class applying the Singleton Pattern still retains its original functionality
- */ }
Well, can this even be called a method? This approach seems non-standard, right? It doesn't matter. This method naturally has its advantages, such as:
1. Highly efficient and thread-safe; 2. Simple and easy to use, no need to consider anything, not even judgment
But its fatal flaw is: resource waste problem. If this object is a huge, extremely resource-intensive object, and we create it at the beginning but don't use it for a long time, that would be quite painful..
Three methods for ensuring thread synchronization are mentioned above. How to choose must be combined with specific circumstances. Efficiency, resource utilization, and various factors must be comprehensively considered
V. Singleton Pattern in Multiple Class Loader Environments
If multiple class loaders exist, multiple class loaders may load our singleton class simultaneously, thus producing multiple instances
For this situation, we can explicitly specify which class loader to use to load the singleton class, thus effectively avoiding the aforementioned problem
VI. Summary
Applying the Singleton Pattern can ensure object uniqueness, but pay attention to the applicable scope of the Singleton Pattern. The Singleton Pattern should not be abused, because after all, resource-sensitive objects that need management won't be very numerous
No comments yet. Be the first to share your thoughts.