Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use singleton mode

2025-01-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article mainly explains "how to use singleton mode". The content of the explanation is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "how to use singleton mode".

Hungry Han style

The hungry Han style is the most common singleton pattern that does not need much consideration, because it has no thread safety problem, that is, the hungry Han style creates an instance object when the class is loaded. The hungry Han style is written as follows:

Public class SingletonHungry {private static SingletonHungry instance = new SingletonHungry (); private SingletonHungry () {} private static SingletonHungry getInstance () {return instance;}}

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 ();});}}

Result

Advantages: thread safety, no need to care about concurrency, and the easiest way to write it.

Cons: the object is created when the class is loaded, that is, whether you use the object or not, the object is created, wasting memory space

Lazy style

The following is the most basic hungry Chinese style of writing, in the case of single-thread, this way is very perfect, but our actual program execution is basically impossible to be single-threaded, so this writing must have thread safety problems.

Public class SingletonLazy {private SingletonLazy () {} private static SingletonLazy instance = null; public static SingletonLazy getInstance () {if (null = = instance) {return new SingletonLazy ();} return instance;}}

Demonstrate 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 ();});}}

Result

As a result, it is obvious that the instance object obtained is not singleton. That is to say, this method of writing is not thread-safe, so it cannot be used in the case of multithreading.

DCL (double check lock)

DCL, that is, Double Check Lock, performs a double check when creating an instance. First, check whether the instance object is empty. If not, lock the current class, and then determine whether the instance is empty. If it is still empty, create the instance. The code is as follows:

Public class SingleTonDcl {private SingleTonDcl () {} private static SingleTonDcl instance = null; public static SingleTonDcl getInstance () {if (null = = instance) {synchronized (SingleTonDcl.class) {if (null = = instance) {instance = new SingleTonDcl ();} return instance }}

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 ();});}}

Result

It is believed that most beginners already feel "classy" when they come into contact with this way of writing. The first thing is to determine whether the instance object is empty, and if it is empty, then take the Class of the object as a lock, so as to ensure that only one thread can access it at the same time, and then determine again whether the instance object is empty, and finally will really initialize and create the instance object. Everything seems to be flawless, but when you have studied JVM, you may see something fishy at a glance. Yes, the problem is instance = new SingleTonDcl (); because this is not an atomic operation, the execution of this sentence is divided into three steps at the JVM level:

1. Allocate memory space for SingleTonDcl 2. Initialize SingleTonDcl instance 3. Point the instance object to the allocated memory space (instance is null)

Normally, the above three steps are performed sequentially, but in fact, JVM may be "amorous" and have to optimize our code, possibly in the order of 1, 3, 2, as shown in the following code

Public static SingleTonDcl getInstance () {if (null = = instance) {synchronized (SingleTonDcl.class) {if (null = = instance) {1. Allocate memory space for SingleTonDcl 3. Point the instance object to the allocated memory space (instance is no longer null) 2. Initialize SingleTonDcl instance} return instance;}

Suppose there are now two threads T1, T2

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

If T1 executes to step 3 above is suspended

Then T2 enters the getInstance method, and because T1 has performed step 3, the instance is no longer empty, so the condition if (null = = instance) is not empty and directly returns instance, but because T1 has not yet performed step 2, the instance at this time is actually a semi-finished product, which will lead to unpredictable risks!

How to solve it? since the problem lies in the possibility of reordering instructions, don't let it be reordered. Isn't that what volatile does? we can add a volatile modifier in front of the instance variable.

Voiceover: the role of volatile 1. Guaranteed object memory visibility 2. Prevent instruction reordering

The optimized code is as follows

Public class SingleTonDcl {private SingleTonDcl () {} / / add the volatile keyword before the object volatile private static SingleTonDcl instance = null; public static SingleTonDcl getInstance () {if (null = = instance) {synchronized (SingleTonDcl.class) {if (null = = instance) {instance = new SingleTonDcl () } return instance;}}

At this point, it seems that the problem has been solved, and the dual locking mechanism + volatile actually basically solves the thread safety problem, ensuring a "real" singleton. But is that really the case? Keep looking down.

Static inner class

Look at the code first.

Public class SingleTonStaticInnerClass {private SingleTonStaticInnerClass () {} private static class HandlerInstance {private static SingleTonStaticInnerClass instance = new SingleTonStaticInnerClass ();} public static SingleTonStaticInnerClass getInstance () {return HandlerInstance.instance;}}

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 () );}}

Characteristics of static inner classes:

This writing uses the JVM class loading mechanism to ensure thread safety; because SingleTonStaticInnerClass is private, there is no way to access it except getInstance (), so it is lazy; at the same time, there is no synchronization when reading the instance, there are no performance defects; and it does not rely on the JDK version.

However, it is still not perfect.

Unsafe singleton

None of the above single examples is perfect for two main reasons

1. Reflex attack

First of all, I would like to mention the reflex mechanism that people love and hate in java. Let's talk less about it. Let's go straight to the code and explain it. Let's take DCL as an example. (why did you choose DCL? because many people think that DCL is the most classy. This is where we start to "hit them in the face")

Modify the test code for the above DCl as follows:

Class C {public static void main (String [] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class singleTonDclClass = SingleTonDcl.class; / / get the constructor of the class Constructor constructor = singleTonDclClass.getDeclaredConstructor (); / / release the private permissions of the constructor to constructor.setAccessible (true) / / reflection creation instance Note: reflection creation should be placed in front before the attack will succeed, because if the reflection attack comes later and the instance is created in the normal way first, it can be judged in the constructor to prevent the reflection attack and throw an exception. / / because the instance has been created in the normal way first, it will enter if SingleTonDcl instance = constructor.newInstance () / / the normal way to obtain the instance is placed after the reflection creates the instance, so that when the reflection is successfully created, the reference in the singleton object is actually empty, and the reflection attack can succeed SingleTonDcl instance1 = SingleTonDcl.getInstance (); System.out.println ("instance1 =" + instance1); System.out.println ("instance =" + instance);}}

I can't believe it's two dates! Is there an extraordinary peace of mind? It's really not what you thought? Other ways are basically similar, and singletons can be destroyed by reflection.

two。 Serialization attack

Let's take the "hungry Han singleton" as an example to demonstrate the serialization and deserialization attack code. First, add the code that implements the Serializable interface to the class corresponding to the hungry Han singleton.

Public class SingletonHungry implements Serializable {private static SingletonHungry instance = new SingletonHungry (); private SingletonHungry () {} private static SingletonHungry getInstance () {return instance;}}

Then look at how to use serialization and deserialization to attack

SingletonHungry instance = SingletonHungry.getInstance (); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("singleton_file")); / / serialization [write] operation oos.writeObject (instance); File file = new File ("singleton_file"); ObjectInputStream ois = new ObjectInputStream (new FileInputStream (file)) / / deserialization [read] operation SingletonHungry newInstance = (SingletonHungry) ois.readObject (); System.out.println (instance); System.out.println (newInstance); System.out.println (instance = = newInstance)

Let's take a look at the result picture.

Sure enough, there are two different objects! In fact, the solution to this deserialization attack is simple, just override the readObject method to be called during deserialization.

Private Object readResolve () {return instance;}

In this way, only one instance of instance is always read during deserialization, which ensures the implementation of the singleton.

The really safe singleton: enumerated mode

Public enum SingleTonEnum {/ * * instance object * / INSTANCE; public void doSomething () {System.out.println ("doSomething");}}

Call method

Public class Main {public static void main (String [] args) {SingleTonEnum.INSTANCE.doSomething ();}}

The singleton implemented by the enumeration pattern is the real singleton pattern, and it is the perfect way to implement it.

One might wonder whether enumerations can also break their singleton implementations through reflection.

Try it and modify the test class of the enumeration

Class E {public static void main (String [] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class singleTonEnumClass = SingleTonEnum.class; Constructor declaredConstructor = singleTonEnumClass.getDeclaredConstructor (); declaredConstructor.setAccessible (true); SingleTonEnum singleTonEnum = declaredConstructor.newInstance (); SingleTonEnum instance = SingleTonEnum.INSTANCE; System.out.println ("instance =" + instance); System.out.println ("singleTonEnum =" + singleTonEnum);}}

Result

There is no non-parametric structure? Let's use the javap tool to check the bytecode to see what the mystery is.

Boy, if you find one with a parameter constructor String Int, give it a try.

/ / when you get the constructor, modify it like this: Constructor declaredConstructor = singleTonEnumClass.getDeclaredConstructor (String.class,int.class)

Result

Boy, an exception was thrown, and the exception message read: "Cannot reflectively create enum objects"

There are no secrets under the source code. Let's see what newInstance () has done. Why would an enumeration created with reflection throw such an exception?

The truth has become known to all! If it is an enumeration, it is not allowed to create it through reflection, which is why it is really safe to create a singleton using enum!

Thank you for your reading. the above is the content of "how to use the singleton pattern". After the study of this article, I believe you have a deeper understanding of how to use the singleton mode, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report