preface
A few days ago experienced an interview, the original plan is to check their level, good guy, this interview directly to me stem from closure. 😭 😭 😭
If you have any confidence in your Java at this point, I suggest you take a look!
Joshua Bloch
The great God said:The best way to implement the singleton pattern is to use enumerations
.
Introduction to the
Singleton Pattern: Ensures that only one class has and only one instance, and provides a global access point.
In real development, there are many objects that we need only one, such as threadPool, cache, default Settings, Registry, log objects, and so on, and it is best to design them as singletons.
The singleton pattern is a widely used design pattern in Java. The singleton pattern has many benefits:
- Avoid duplicate object creation.
- Reduce the time overhead each time an object is created.
- Saving memory space (such as spring-managed statelessness
bean
). - Avoid logical errors caused by operations between multiple instances.
- If an object is likely to span the entire application, it also serves as a global management configuration.
methods
There are many ways to write the singleton pattern, but many of them have some shortcomings, which are pointed out in the following examples.
Lazy (thread unsafe)
public class Singleton {
private static Singleton instance;
private Singleton (a){} // Private constructor
public static Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
returninstance; }}Copy the code
Lazy loading is obvious, but it is prohibited because it is considered thread-safe.
Lazy (thread-safe)
public class Singleton {
private static Singleton instance;
private Singleton (a){}
public static synchronized Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton();
}
returninstance; }}Copy the code
This method uses the synchronized keyword to ensure thread-safety, but it’s inefficient, because 99.99% of the time you don’t need to synchronize, and it’s a bit too hard. Highly not recommended!
The hungry
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (a){}
public static Singleton getInstance(a) {
returninstance; }}Copy the code
This ClassLoader-based approach b avoids multithreading synchronization issues and loads classes at initialization time. This is currently the simplest implementation.
Hungry Man (Variant)
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (a){}
public static Singleton getInstance(a) {
returninstance; }}Copy the code
Similar to the above, except that the instance is initialized during class initialization.
Static inner class
public class Singleton {
private static class SingletonHolder { // Static inner class
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (a){}
public static final Singleton getInstance(a) {
returnSingletonHolder.INSTANCE; }}Copy the code
The implementation in hungry mode does not have the effect of lazy loading. This way the class is loaded, but not initialized immediately, because the static inner class is not actively used. The SingletonHolder class is loaded only when getInstance() is explicitly called, which is obviously lazy.
Double check lock (slacker)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (a){}
public static Singleton getSingleton(a) {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) { // Note that there is also a void ~
singleton = newSingleton(); }}}returnsingleton; }}Copy the code
The use of volatile ensures visibility between threads, a method commonly known as double-checked locking. Both efficiency and security are guaranteed, but the code is more complex but looks more advanced.
The enumeration
public class Singleton {
private Singleton (a){}
public static Singleton getInstance(a) {
returnEnumSingleton.getInstance(); }}public enum EnumSingleton {
INSTANCE;
private final Singleton insatnce;
EnumSingleton() {
insatnce = new Singleton();
}
private Singleton getInstance(a) {
returninstance; }}Copy the code
This approach, advocated by Josh Bloch, author of Effective Java, not only avoids thread synchronization problems, but also prevents serialization from recreating new objects. So this way of writing is highly recommended and optimal.
So each of the previous ways to implement the singleton pattern has the following three characteristics:
- Privatize constructors.
- Instantiated variable references are privatized.
- There are two ways to get a variable.
- On the first point
Privatization of constructors
It is not safe because it cannot withstandReflection attack
For example:
public class Singleton implements Serializable {
private static Singleton instance = new Singleton();
private Singleton(a) {}
public static Singleton getInstance(a) {
returninstance; }}public class Main(a) {
public static void main(String[] args) {
Singleton s = new Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); // Get all constructors, including non-public ones
constructor.setAccessible(true);
Singleton sReflection = constructor.newInstance(); // Use the empty constructor to new an instance. Even if it's private
System.out.println(s); // cn.vgbhfive.beans.Singleton@1f32e575
System.out.println(sReflection); // cn.vgbhfive.beans.Singleton@279f2327
System.out.println(s == sReflection); // false}}Copy the code
By reflection, a new instance object is created for the so-called singleton. So there are still unsafe factors in this way.
- Let’s see if the serialization and deserialization of the previous methods are problematic as follows:
public class Main {
public static void main(String[] args) {
Singleton instance = new Singleton();
byte[] serialize = SerializationUtils.serialize(instance);
Object deserialize = SerializationUtils.deserialize(serialize);
System.out.println(instance); // cn.vgbhfive.beans.Singleton@1f32e575
System.out.println(deserialize); // cn.vgbhfive.beans.Singleton@279f2327
System.out.println(instance == deserialize); // false}}Copy the code
The result shows that the two objects are not equal before and after serialization, so serialization is not safe.
- Finally, we test whether enumeration is safe in serialization and deserialization, as follows:
public class Main {
public static void main(String[] args) {
Singleton instance = EnumSingleton.INSTANCE;
byte[] serialize = SerializationUtils.serialize(instance);
Object deserialize = SerializationUtils.deserialize(serialize);
System.out.println(instance);
System.out.println(deserialize);
System.out.println(instance == deserialize); // true}}Copy the code
As is clear from the above results, enumerated types are safe for serialization and deserialization.
- The security of enumerations in getting new instances by reflection lies in the following aspects:
- Null constructor.
- Enumeration classes check if an object exists when they create it
ENUM
Modification.
You can have a look at the detailsEnum
Related source code.
- To sum up, it can be concluded that:
Enumerations are a best practice for implementing the singleton pattern
. The advantages are as follows:
- Reflection is safe.
- Serialization and deserialization security.
- It’s easy to write.
- Nothing else is enough to justify not using enumerations.
conclusion
As one of the simplest and easiest design patterns to understand, singleton pattern is widely used in many places and has a great probability to be encountered in interviews. (Like me)
Effective Java This is a great book, recommended reading, and the book I bought is already on its way.
Personal note
The contents of this blog are all notes made by the author to learn, invade and delete! If used for other purposes, please specify the source!