Follow the public account “Java backend technology full Stack” reply “000” to obtain a large number of e-books
The story
To be honest, there are hundreds of versions of the singleton pattern online. You’ve probably seen many versions of it. But what does it matter? A buddy in my tech group interviewed last week for a singleton and sent him back to wait for notice.
Here’s the question the student was asked:
-
What are the characteristics of singletons?
-
Do you know the specific usage scenarios of singleton pattern?
-
How many ways are singletons commonly written?
-
How to ensure thread safety?
-
How can it not be hit by reflex?
-
How to guarantee against serialization and deserialization attacks?
-
Will enumerations be serialized?
.
You can also try answering these questions and see how many you can answer.
define
The Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern is the creation pattern, which provides the best way to create objects.
This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique objects directly, without instantiating the objects of the class.
Features:
-
1. A singleton class can have only one instance.
-
2. A singleton class must create its own unique instance.
-
3. The singleton class must provide this instance to all other objects
-
4. Hide all construction methods
** Purpose: ** Ensures that a class has only one instance and provides a global access point to access it.
Case study: A company can only have one CEO, and having more than one CEO is a mess.
Usage scenarios
You need to ensure that there is absolutely only one instance in any case.
Examples include ServletContext, ServletConfig, ApplicationContext, DBTool, and so on.
Writing the singleton pattern
-
The hungry type
-
Lazy (includes double-checked locks, static inner classes)
-
Registration (using enumeration as an example)
The hungry type
As the name suggests, hungry people: Hungry people need to eat first, so, it’s done from the start.
Static, static, static, static, static, static, static
public class HungrySingleton{ private static final HungrySingleton INSTANCE; static { INSTANCE=new HungrySingleton(); } // private static final HungrySingleton INSTANCE=new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance(){ return INSTANCE; }}Copy the code
The hungry style has a fatal drawback: it wastes space and does not need to be instantiated. Imagine the horror of thousands of them doing the same.
And then, you know, can you instantiate it when you use it, and that leads to slacker style.
LanHanShi
As the name implies, it is needed to create, because lazy, you do not call my method, I will not work.
Here is a lazy Java code implementation:
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(a) {}public static LazySingleton getInstance(a) {
if (lazySingleton == null) {/ / 01
lazySingleton = new LazySingleton();/ /.
}
returnlazySingleton; }}Copy the code
Enter the getInstance method, determine if lazySingleton is empty, create an object if it is, and return it.
But here’s the thing:
Both threads enter the getInstance method at the same time, each executes line 01, both true, each creates an object, and then returns the object they created.
Isn’t it not satisfying to have only one object? So there are thread-safety issues, so how do you solve them?
The first thing I think of is locking. Hence the thread-safe lazy loading version:
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(a) {}// A simple and crude solution to thread-safety problems
// Performance issues still exist
public synchronized static LazySingleton getInstance(a) {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
returnlazySingleton; }}Copy the code
The getInstance method is marked with synchronized, but it also involves the problem of lock. Synchronization lock is a good influence on system performance. Although it has been optimized after JDK1.6, it still involves the cost of lock.
Every time a thread calls the getInstance method, a lock is involved, so this has been optimized to become the familiar double-checked lock.
Double check lock
The code implementation is as follows:
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(a) {}public static LazyDoubleCheckSingleton getInstance(a) {
if (lazyDoubleCheckSingleton == null) {/ / 01
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {/ /.
lazyDoubleCheckSingleton = newLazyDoubleCheckSingleton(); }}}returnlazyDoubleCheckSingleton; }}Copy the code
In this code, at line 01, if it’s not empty, it just returns, so that’s the first check. If it is empty, the synchronization block is entered, and line 02 is checked again.
Double checking is the real if judgment, the acquisition of class object lock, if judgment.
The above code appears to have some problems, such as instruction reorder (requires JVM knowledge).
What does reordering mean?
Like a simple sentence in Java
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
Copy the code
The JVM instructions are compiled by the compiler as follows:
memory =allocate(); //1: allocates memory space for the object
ctorInstance(memory); //2: initializes the object
instance =memory; //3: Sets instance to the newly allocated memory address
However, the order of these instructions is not fixed, and may be optimized by the JVM and CPU to rearrange the instructions in the following order:
memory =allocate(); //1: allocates memory space for the object
instance =memory; //3: Sets instance to the newly allocated memory address
ctorInstance(memory); //2: initializes the object
To prevent instruction reordering, we can use volatile (note that volatile prevents instruction reordering and thread visibility).
So, a better version came out.
public class LazyDoubleCheckSingleton {
// Use volatile modifier
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(a) {}public static LazyDoubleCheckSingleton getInstance(a) {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = newLazyDoubleCheckSingleton(); }}}returnlazyDoubleCheckSingleton; }}Copy the code
Although it is a significant improvement over the previous version, there are still synchronization locks, which can still affect performance. Therefore, it is optimized to static inner class mode:
Static inner class
Here is the code implementation of the static inner class:
public class LazyStaticSingleton {
private LazyStaticSingleton(a) {}public static LazyStaticSingleton getInstance(a) {
return LazyHolder.LAZY_STATIC_SINGLETON;
}
// Wait until the external method call is executed
// Make use of inner class features
//JVM low-level execution perfectly circumvented thread-safety issues
private static class LazyHolder {
private static final LazyStaticSingleton LAZY_STATIC_SINGLETON = newLazyStaticSingleton(); }}Copy the code
The use of internal classes is a perfect way to circumvent thread-safety issues in the UNDERLYING JVM, which is the preferred approach in many projects today.
But there are potential risks. What are the risks?
It is possible to use string modification with reflective violence, as well as create multiple instances:
The reflection code is implemented as follows:
import java.lang.reflect.Constructor;
public class LazyStaticSingletonTest {
public static void main(String[] args) {
try{ Class<? > clazz = LazyStaticSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(null);
// Forcible access
constructor.setAccessible(true);
Object object = constructor.newInstance();
Object object1 = LazyStaticSingleton.getInstance();
System.out.println(object == object1);
} catch(Exception ex) { ex.printStackTrace(); }}}Copy the code
This code runs as false.
So, the above double-checked lock approach, via reflection, still has potential risks. What to do?
In The book Effect Java, the author recommends using enumerations to implement the singleton pattern because they cannot be reflected.
The enumeration
Here is the code implementation of the enumerated singleton pattern:
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData(a) {
return data;
}
public static EnumSingleton getInstance(a){
returnINSTANCE; }}Copy the code
We use the code reflected above to test the enumerated singleton pattern.
public class EnumTest {
public static void main(String[] args) {
try{ Class<? > clazz = EnumSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(null);
// Forcible access
constructor.setAccessible(true);
Object object = constructor.newInstance();
Object object1 = EnumSingleton.getInstance();
System.out.println(object == object1);
} catch(Exception ex) { ex.printStackTrace(); }}}Copy the code
Run this code:
java.lang.NoSuchMethodException: com.tian.my_code.test.designpattern.singleton.EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.tian.my_code.test.designpattern.singleton.EnumTest.main(EnumTest.java:41)
Copy the code
You really can’t do it with reflection. If the interviewer is at this point, why can’t enumerations be reflected?
Why can’t enumerations be reflected?
We’re in reflection code
Constructor constructor = clazz.getDeclaredConstructor(null);
Copy the code
This line of code gets its no-argument constructor. Also, from the error log, we can see that the error occurred in the getConstructor0 method, and that the no-argument constructor was not found.
Oddly enough, enumerations are classes, so if we don’t define a constructor for a class display, we’ll be given a constructor with no arguments by default.
So I came up with an idea: we could decompile the.class file of our enumeration singleton using jad.
Find the directory where our class file is located, and then we can execute the following command:
C:\Users\Administrator>jad D:\workspace\my_code\other-local-demo\target\classes
com\tian\my_code\test\designpattern\singleton\EnumSingleton.class
Parsing D:\workspace\my_code\other-local-demo\target\classes\com\tian\my_code\t
st\designpattern\singleton\EnumSingleton.class... Generating EnumSingleton.jad
Copy the code
Note: the class file directory and the directory where the jad file is generated.
Then open the enumsingleton.jad file:
So, I thought, let’s use the parameter constructor to create:
public class EnumTest {
public static void main(String[] args) {
try{ Class<? > clazz = EnumSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);
// Forcible access
constructor.setAccessible(true);
Object object = constructor.newInstance("Tian Weichang".996);
Object object1 = EnumSingleton.getInstance();
System.out.println(object == object1);
} catch(Exception ex) { ex.printStackTrace(); }}}Copy the code
Running the code again results in:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java: 417).at com.tian.my_code.test.designpattern.singleton.EnumTest.main(EnumTest.java45) :Copy the code
The hint is obvious: don’t use reflection to create enumerated objects.
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if(! override) {if(! Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<? > caller = Reflection.getCallerClass(); checkAccess(caller, clazz,null, modifiers); }}//Modifier.ENUM is used to check whether the Modifier is enumerated
if((clazz.getModifiers() & Modifier.ENUM) ! =0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
Copy the code
So, here, we are really clear, why enumeration does not let reflection reasons.
Serialization destruction
Let’s demonstrate this in a non-thread-safe hanky-hank fashion to see how serialization breaks the pattern.
public class ReflectTest {
public static void main(String[] args) {
Singleton1 receives deserialized instances from the input stream
HungrySingleton singleton1 = null;
HungrySingleton singleton2 = HungrySingleton.getInstance();
try {
/ / the serialization
FileOutputStream fos = new FileOutputStream("HungrySingleton.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton2);
oos.flush();
oos.close();
// deserialize
FileInputStream fis = new FileInputStream("HungrySingleton.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
singleton1 = (HungrySingleton) ois.readObject();
ois.close();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton1 == singleton2);
} catch(Exception e) { e.printStackTrace(); }}}Copy the code
Running results:
com.tian.my_code.test.designpattern.singleton.HungrySingleton@7e6cbb7a
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
false
Copy the code
See?
Using serialization can break patterns in ways that may not be clear to many people.
How to prevent it?
We make a slight change to the non-thread-safe hanky code:
public class HungrySingleton implements Serializable{
private static final HungrySingleton INSTANCE;
static {
INSTANCE=new HungrySingleton();
}
private HungrySingleton(a){}public static HungrySingleton getInstance(a){
return INSTANCE;
}
// Add the readResolve method and return INSTANCE
privateObject readResolve method, and returns (){returnINSTANCE; }}Copy the code
Running the serialization test again gives the following result:
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
com.tian.my_code.test.designpattern.singleton.HungrySingleton@452b3a41
true
Copy the code
Hey, so we don’t have to create just one instance?
Answer:
Another method, the readObject0(false) method, is called in the readObject() method of the ObjectInputStream class. The checkResolve(readOrdinaryObject(unshared)) method is called in the readObject0(false) method.
There is this code in the readOrdinaryObject method:
Object obj;
try {
// If there are constructors, create instance if there are constructors
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
...
}
// Determine if the singleton class has a readResolve method
if (desc.hasReadResolveMethod()) {
Object rep = desc.invokeReadResolve(obj);
}
/ / invokeReadResolve method
if(readResolveMethod ! =null) {
// calls readResolve in our singleton class and returns the object returned by that method
// Note: this is a no-parameter method
return readResolveMethod.invoke(obj, (Object[]) null);
}
Copy the code
Create an instance and then check to see if our singleton class has a readResolve method that takes no arguments
private Object readResolve(a){
return INSTANCE;
}
Copy the code
conclusion
We override the readResolve() no-argument method to create only two instances of what appears to be one instance.
Next, the interviewer asks: Can enumeration singletons be serialized?
Can enumerated singletons be serialized?
Answer: can’t be destroyed, see me slowly give you a way.
Don’t talk,show me the code.
Let’s first verify that it really can’t be broken, look at the code:
public class EnumTest {
public static void main(String[] args) {
Singleton1 receives deserialized instances from the input stream
EnumSingleton singleton1 = null;
EnumSingleton singleton2 = EnumSingleton.getInstance();
try {
/ / the serialization
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleton2);
oos.flush();
oos.close();
// deserialize
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
singleton1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton1 == singleton2);
} catch(Exception e) { e.printStackTrace(); }}}Copy the code
Running results:
INSTANCE
INSTANCE
true
Copy the code
Indeed, enumeration singletons are not corrupted by serialization, so why? Well, there’s got to be a reason.
Another method, the readObject0(false) method, is called in the readObject() method of the ObjectInputStream class. The checkResolve(readOrdinaryObject(unshared)) method is called in the readObject0(false) method.
case TC_ENUM:
return checkResolve(readEnum(unshared));
Copy the code
In the readEnum method
privateEnum<? > readEnum(boolean unshared) throws IOException {
if(bin.readByte() ! = TC_ENUM) {throw newInternalError(); } Class<? > cl = desc.forClass();if(cl ! =null) {
try {
@SuppressWarnings("unchecked")
/ / the keyEnum<? > en = Enum.valueOf((Class)cl, name); result = en;/ /... Other code omitted}}}public static <T extends Enum<T>> T valueOf(Class
enumType, String name)
{
/ / enumType. EnumConstantDirectory () returns a HashMap
// Get from HashMap
T result = enumType.enumConstantDirectory().get(name);
if(result ! =null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
// Return a HashMap
Map<String, T> enumConstantDirectory(a) {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
// Use HashMap
Map<String, T> m = new HashMap<>(2 * universe.length);
for(T constant : universe) m.put(((Enum<? >)constant).name(), constant); enumConstantDirectory = m; }return enumConstantDirectory;
}
Copy the code
So, the enumeration singleton uses Map
, and the key of the Map is INSTANCE in our enumeration class. Because of the uniqueness of the Map’s key, a unique instance is then created. This enumerated singleton is also known as the registered singleton.
This registered singleton pattern is also widely used in Spring, where the IOC container is a typical example.
conclusion
This paper describes the definition of singleton pattern, singleton pattern writing method. Singleton mode thread safety problem solving, reflection damage, deserialization damage, etc.
Note: Do not use design patterns for the sake of applying them. Rather, it’s natural to think of the single design pattern as a shortcut when you run into business problems.
Advantages and disadvantages of the singleton pattern
advantages
Having only one instance in memory reduces memory overhead. Multiple occupancy of resources can be avoided. Set global access points to strictly control access.
disadvantages
No excuses, poor scalability. If you want to extend a singleton, there is no other way but to modify the code.
The singleton pattern is inconsistent with the open – close principle.
knowledge
Summary of key knowledge of singleton pattern:
-
Privatized constructor
-
Ensuring thread safety
-
Lazy loading
-
Preventing reflex attacks
-
Prevent serialization and deserialization from breaking
If there is any gain, please click a like, see, also suggested to share with everyone to see the singleton pattern.