This article has been included in my Github address: github.com/allentofigh… , welcome everyone to pay attention and give a STAR, this is very important to me, thank you for your support! After the code sea each article will be included in this address to facilitate everyone to consult!
preface
The singleton pattern is the simplest and most basic design pattern. Even for a beginner developer, when asked which design patterns he has used, he will probably say singleton. But do you think the basic singleton pattern is really that simple? You might ask, “What about a simple singleton pattern?” Ha ha, no more words, let us wait and see, adhere to see, I believe you will have harvest!
The hungry type
Hunger-style is the most common and least concerned singleton pattern because it does not have thread-safety issues. Hunger-style creates instance objects as soon as the class is loaded. The hungry Chinese is written as follows:
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(a) {}private static SingletonHungry getInstance(a) {
returninstance; }}Copy the code
- The test code is as follows:
class A {
public static void main(String[] args) {
IntStream.rangeClosed(1.5)
.forEach(i -> {
new Thread(
() -> {
SingletonHungry instance = SingletonHungry.getInstance();
System.out.println("instance = "+ instance); } ).start(); }); }}Copy the code
Advantages: Thread-safe, no need to worry about concurrency, and the simplest to write.
Disadvantages: The object is created when the class is loaded, which means that the object will be created regardless of whether you use it or not, wasting memory
LanHanShi
Type of writing, the following is the most basic a starving man, in a single thread, this way is very perfect, but our actual program execution basic it can’t be a single thread, so this kind of writing will thread safety problems
public class SingletonLazy {
private SingletonLazy(a) {}private static SingletonLazy instance = null;
public static SingletonLazy getInstance(a) {
if (null == instance) {
return new SingletonLazy();
}
returninstance; }}Copy the code
Demonstrating multithreaded execution
class B {
public static void main(String[] args) {
IntStream.rangeClosed(1.5)
.forEach(i -> {
new Thread(
() -> {
SingletonLazy instance = SingletonLazy.getInstance();
System.out.println("instance = "+ instance); } ).start(); }); }}Copy the code
As a result, it is clear that the obtained instance object is not a singleton. That is, it is not thread-safe and therefore cannot be used in multithreaded situations
DCL (Double check lock)
DCL Double Check Lock is a Double Check when creating an instance, first Check whether the instance object is empty, if not, Lock the current class, then Check whether the instance is empty, if still empty, create the instance. The code is as follows:
public class SingleTonDcl {
private SingleTonDcl(a) {}private static SingleTonDcl instance = null;
public static SingleTonDcl getInstance(a) {
if (null == instance) {
synchronized (SingleTonDcl.class) {
if (null == instance) {
instance = newSingleTonDcl(); }}}returninstance; }}Copy the code
The test code is as follows:
class C {
public static void main(String[] args) {
IntStream.rangeClosed(1.5)
.forEach(i -> {
new Thread(
() -> {
SingleTonDcl instance = SingleTonDcl.getInstance();
System.out.println("instance = "+ instance); } ).start(); }); }}Copy the code
If it is empty, then the Class of the object is used as a lock, so that only one thread can access the object at a time. Then again, determine whether the instance object is empty. Finally, the instantiation object is actually initialized. It may seem like it’s all right, but once you’ve learned about the JVM you’ll probably see it at a glance. Instance = new SingleTonDcl(); Since this is not an atomic operation, this sentence is executed in three steps at the JVM level:
Allocate memory space for SingleTonDcl. 2. Initialize SingleTonDcl instance 3. Reference instance object to allocated memory space (instance is null)
Normally the above three steps are executed sequentially, but in practice the JVM may have to optimize our code in a random order of 1, 3, and 2, as shown in the code below
public static SingleTonDcl getInstance(a) {
if (null == instance) {
synchronized (SingleTonDcl.class) {
if (null == instance) {
1.Allocate memory space to SingleTonDcl3.Refers an instance object to the allocated memory space (instance is notnullA)2.Initialize SingleTonDcl instance}}}return instance;
}
Copy the code
Let’s say I have two threads, T1, t2
- If T1 is suspended after performing step 3 above
- If (null == instance) t2 then enters the getInstance method. T1 performs step 3 and instance is not null. Instance is returned directly, but since T1 has not performed step 2, instance is actually a work in progress, which will lead to unpredictable risks!
Since the problem is that the instruction can be reordered, why not not make it reordered? That’s what volatile is for. We could add a volatile modifier to the instance variable
Voiceover: What volatile does 1. Ensure object memory visibility 2. Prevents instruction reorderingCopy the code
The optimized code looks like this
public class SingleTonDcl {
private SingleTonDcl(a) {}// Add the volatile keyword in front of the object
volatile private static SingleTonDcl instance = null;
public static SingleTonDcl getInstance(a) {
if (null == instance) {
synchronized (SingleTonDcl.class) {
if (null == instance) {
instance = newSingleTonDcl(); }}}returninstance; }}Copy the code
At this point, the problem seems to be solved, and the double locking + volatile mechanism does in fact basically solve the thread-safety problem, ensuring “true” singletons. But is that really the case? Keep reading
Static inner class
Look at the code
public class SingleTonStaticInnerClass {
private SingleTonStaticInnerClass(a) {}private static class HandlerInstance {
private static SingleTonStaticInnerClass instance = new SingleTonStaticInnerClass();
}
public static SingleTonStaticInnerClass getInstance(a) {
returnHandlerInstance.instance; }}Copy the code
- The test code is as follows:
class D {
public static void main(String[] args) {
IntStream.rangeClosed(1.5)
.forEach(i->{
new Thread(()->{
SingleTonStaticInnerClass instance = SingleTonStaticInnerClass.getInstance();
System.out.println("instance = "+ instance); }).start(); }); }}Copy the code
Static inner class features:
This approach uses the JVM class loading mechanism to ensure thread safety; Since SingleTonStaticInnerClass is private, in addition to the getInstance () there is no way to access it, so it is LanHanShi; There is no synchronization when reading instances at the same time, no performance defects; Nor does it depend on the JDK version;
Still, it’s not perfect.
Unsafe singleton
None of the above implementation singletons is perfect for two main reasons
1. Reflex attacks
First of all, we will mention Java’s love-hate reflection mechanism. Without further ado, we will go directly to the side of the code. Here we will use DCL as an example (why choose DCL because many people think that the DCL is the best written…. This is where we go to “punch them in the face”)
Modify the above DCl test code as follows:
class C {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<SingleTonDcl> singleTonDclClass = SingleTonDcl.class;
// Get the constructor for the class
Constructor<SingleTonDcl> constructor = singleTonDclClass.getDeclaredConstructor();
// Set constructor private permissions
constructor.setAccessible(true);
// Reflection create instance (); reflection create instance (); reflection create instance ();
// Since the instance has been created in the normal way, it will enter if
SingleTonDcl instance = constructor.newInstance();
The normal way to get an instance is after the reflection creation instance, so that when reflection is successfully created, the reference in the singleton is actually empty for the reflection attack to succeed
SingleTonDcl instance1 = SingleTonDcl.getInstance();
System.out.println("instance1 = " + instance1);
System.out.println("instance = "+ instance); }}Copy the code
Two objects! Is your heart at peace? It’s not what you thought it was, is it? The other methods are basically similar and can destroy singletons through reflection.
2. Serialization attacks
The serialization and deserialization attack code is shown in the example of “Hanchman singleton”. First, add code to the corresponding class of hanchman singleton implementing Serializable interface.
public class SingletonHungry implements Serializable {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(a) {}private static SingletonHungry getInstance(a) {
returninstance; }}Copy the code
And then how do you attack with serialization and deserialization
SingletonHungry instance = SingletonHungry.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")));
// serialize the write operation
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))
// Deserialize the read operation
SingletonHungry newInstance = (SingletonHungry) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
Copy the code
So let’s see what happens
Sure enough, there were two different objects! The solution to this deserialization attack is simply to override the readObject method to be called during deserialization
private Object readResolve(a){
return instance;
}
Copy the code
In this way, only one instance is read in deserialization, ensuring the implementation of singleton.
Truly safe singleton: enumeration mode
public enum SingleTonEnum {
/** * instance object */
INSTANCE;
public void doSomething(a) {
System.out.println("doSomething"); }}Copy the code
A method is called
public class Main {
public static void main(String[] args) { SingleTonEnum.INSTANCE.doSomething(); }}Copy the code
The singleton implemented by the enumeration pattern is the true singleton pattern and is the perfect implementation
One might wonder: Can enumerations also break their singleton implementations through reflection?
Try modifying enumerated test classes
class E{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<SingleTonEnum> singleTonEnumClass = SingleTonEnum.class;
Constructor<SingleTonEnum> declaredConstructor = singleTonEnumClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingleTonEnum singleTonEnum = declaredConstructor.newInstance();
SingleTonEnum instance = SingleTonEnum.INSTANCE;
System.out.println("instance = " + instance);
System.out.println("singleTonEnum = "+ singleTonEnum); }}Copy the code
No no-parameter construction? Let’s use the Javap tool to look up the bytecode and see what’s going on
Well, there’s a String Int constructor with arguments, so let’s try it out
// Get the constructor to look like this
Constructor<SingleTonEnum> declaredConstructor = singleTonEnumClass.getDeclaredConstructor(String.class,int.class);
Copy the code
Exception: Cannot reflectively create enum objects
There is no secret in the source code, so what does newInstance() actually do? Why does creating an enumeration with reflection throw such an exception?
The truth is out! If it is an enumeration, it is not allowed to be created by reflection, which is why it is really safe to create singletons using enums!
conclusion
Above is some knowledge about the singleton pattern, you really don’t look down upon this little singleton, the interview most candidates write wrong so a simple singletons, writing for the majority of also only DCL, but ask whether have what not safe, how to write secure use enum singleton, almost no one can answer it! Some people say you can write DCL, but why bother? But WHAT I want to say is that it is precisely this kind of spirit that enables you to gradually accumulate technical depth and become an expert. You have the persistence to explore the technology. Why can’t you worry about becoming an expert?
Finally, welcome everyone to pay attention to my official number, add my friend: “Geekoftaste”, communicate together, common progress!